27 顺序表 · 链表

目录

一、单链表

(一)概念

1、节点

2、链表的性质

(二)单链表的实现

(三)单链表算法题

1、移除链表元素

2、反转链表

3、链表的中间节点

4、合并两个有序的单链表

5、链表分割

6、链表的回文结构

7、相交链表

8、环形链表 · 是否是环形链表

9、环形链表 · 寻找环形起始点

10、随机链表的复制

二、链表的分类

三、双向链表

(一)概念与结构

(二)双向链表的实现

四、顺序表与链表的分析


一、单链表

(一)概念

        概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

        总结:链表的逻辑结构是线性的,数据是一个接着一个的;物理结构不一定是线性的。

1、节点

        与顺序表不同的是,链表里的每个元素都是独立申请下来的空间,我们称之为“结点”。

        单链表的结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量),也称为数据域和指针域。

        在单链表中需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。

2、链表的性质

        ① 链式结构在逻辑上是连续的,在物理结构上不一定连续。

        ② 节点一般是从堆上申请的。(通过动态内存管理进行开辟空间)

        ③ 从堆上开辟的空间,是按照一定策略分配出来的,每次开辟的空间可能连续,也可能不连续。

(二)单链表的实现

        单链表常用名:SingleList,简称SLT;单链表节点常用名字:SLTNode。

        定义链表结构体,其实是在定义节点的结构体,因为链表是由一个个的节点组合起来的。

单链表的编写:

① 头文件:定义单链表的节点结构体,声明要提供的操作(起到目录作用);

② cpp文件:编写具体实现各种操作(起到内容作用);

③ 测试文件:测试是否可以正常运行。

        SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<iostream>
using namespace std;typedef int SLTDataType;
typedef struct SLTNode
{SLTDataType data;struct SLTNode* next;
}SLTNode;//一、创建新节点、打印链表、查找节点
//	(一)创建新节点
SLTNode* create_node(SLTDataType num);
//	(二)打印链表
void SLTPrint(SLTNode* phead);
//	(三)查找节点
SLTNode* find_node(SLTNode* phead, SLTDataType num);//二、插入数据(传过来的单链表可以为空,单一定要创建新节点)
//	(一)尾插
void SLTPushBack(SLTNode*& phead, SLTDataType num);
//	(二)头插
void SLTPushFront(SLTNode*& phead, SLTDataType num);
//	(三)指定位置插
//		1、指定位置之前插
void SLTInsert(SLTNode*& phead, SLTNode* pos, SLTDataType num);
//		2、指定位置之后插
void SLTInsertAfter(SLTNode* pos, SLTDataType num);//三、删除数据(要判断传过来的节点是否为空)
//	(一)尾删
void SLTPopBack(SLTNode*& phead);
//	(二)头删
void SLTPopFront(SLTNode*& phead);
//	(三)指定位置删除
//		1、指定位置删除
void SLTErase(SLTNode*& phead, SLTNode* pos);
//		2、指定位置之后删除
void SLTEraseAfter(SLTNode* pos);
//	(四)销毁单链表
void SLTDestroy(SLTNode*& phead);

        SList.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"//一、创建新节点、打印链表、查找节点
//	(一)创建新节点
SLTNode* create_node(SLTDataType num)
{SLTNode* new_node = (SLTNode*)malloc(sizeof(SLTNode));if (new_node == nullptr){perror("creat_node fail");exit(1);}new_node->data = num;new_node->next = nullptr;
}
//	(二)打印链表
void SLTPrint(SLTNode* phead)
{while (phead){cout << phead->data << " -> ";phead = phead->next;}cout << "nullptr" << endl;
}
//	(三)查找节点
SLTNode* find_node(SLTNode* phead, SLTDataType num)
{while (phead){if (phead->data == num)return phead;phead = phead->next;}
}//二、插入数据(传过来的单链表可以为空,单一定要创建新节点)
//	(一)尾插
void SLTPushBack(SLTNode*& phead, SLTDataType num)
{SLTNode* new_node = create_node(num);if (phead == nullptr)phead = new_node;else{SLTNode* node = phead;while (node->next)node = node->next;node->next = new_node;new_node->next = nullptr;}
}
//	(二)头插
void SLTPushFront(SLTNode*& phead, SLTDataType num)
{SLTNode* new_node = create_node(num);if (phead == nullptr)phead = new_node;else {new_node->next = phead;phead = new_node;}
}
//	(三)指定位置插
//		1、指定位置之前插
void SLTInsert(SLTNode*& phead, SLTNode* pos, SLTDataType num)
{assert(pos);SLTNode* new_node = create_node(num);if (phead == pos){new_node->next = phead;phead = new_node;}else{SLTNode* pre_node = phead;while (pre_node){if (pre_node->next == pos){new_node->next = pre_node->next;pre_node->next = new_node;break;}pre_node = pre_node->next;}}
}
//		2、指定位置之后插
void SLTInsertAfter(SLTNode* pos, SLTDataType num)
{assert(pos);SLTNode* new_node = create_node(num);new_node->next = pos->next;pos->next = new_node;
}//三、删除数据(要判断传过来的节点是否为空)
//	(一)尾删
void SLTPopBack(SLTNode*& phead)
{assert(phead);if(phead->next == nullptr){free(phead);phead == nullptr;}else{SLTNode* pre_node = phead;while (pre_node->next->next)pre_node = pre_node->next;SLTNode* node = pre_node->next;pre_node->next = nullptr;free(node);node = nullptr;}
}
//	(二)头删
void SLTPopFront(SLTNode*& phead)//若不传引用,则会造成二次free
{assert(phead);SLTNode* node = phead->next;free(phead);phead = node;
}
//	(三)指定位置删除
//		1、指定位置删除
void SLTErase(SLTNode*& phead, SLTNode* pos)
{assert(phead && pos);if (phead == pos){phead = phead->next;free(pos);pos = nullptr;}else{SLTNode* pre_node = phead;while (pre_node){if (pre_node->next == pos){pre_node->next = pos->next;free(pos);pos = nullptr;break;}pre_node = pre_node->next;}}
}
//		2、指定位置之后删除
void SLTEraseAfter(SLTNode* pos)
{assert(pos);SLTNode* node = pos->next;pos->next = node->next;free(node);node = nullptr;
}
//	(四)销毁单链表
void SLTDestroy(SLTNode*& phead)
{assert(phead);SLTNode* next_noed = phead->next;while (next_noed){free(phead);phead = next_noed;next_noed = next_noed->next;}free(phead);phead = nullptr;
}

        test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"void test()
{//SLTNode* re =  create_node(15);SLTNode* plist = nullptr;for (int i = 1; i < 11; i++){SLTPushBack(plist, i);//SLTPushFront(plist, i);}SLTPrint(plist);SLTNode* re = find_node(plist, 5);//SLTInsert(plist, re, 255);//SLTInsertAfter(re, 255);//for (int i = 0; i < 1; i++)//{//	//SLTPopBack(plist);//	//SLTPopFront(plist);//}//SLTErase(plist, re);//SLTEraseAfter(re);SLTDestroy(plist);SLTPrint(plist);
}int main()
{test();return 0;
}

        总结:

        ① 单链表没办法从右向左遍历,只能从左向右遍历,若是对单链表进行操作,需要影响到目标节点的前一个节点时,需要传递头节点进行遍历,找到目标节点的前一个节点进行修改。

        ② 在链表中,没有增容的概念(不使用realloc),需要插入新的数据,直接申请一个节点大小的空间就可以了。

        ③ 判断传参传的是一级指针还是二级指针(使用 “引用” ),就要看是否改变了实参地址指向的空间(这个改变是指实参所指向的空间不会变成另一个地址的空间)。

        比如:

        在尾插中存在两种情况,第一种是存在节点的时候,直接进行尾插,不需要改变传过来的头节点的指针指向;而第二种情况就是头指针指向空的时候,需要把头指针指向空地址改成指向新开辟的空间的地址,这样就改变了实参指向的地址空间,所以在尾插操作中需要传二级指针(使用 “引用” )。

        而在删除指定节点之后的节点的操作中,并没有改变对指定节点的空间进行改变,所以传一级指针即可。

(三)单链表算法题

1、移除链表元素

        题目链接如下:

                https://leetcode.cn/problems/remove-linked-list-elements/description/

        解题思路:

                创建新链表,然后遍历原链表,把除了【指定移除节点】的其他节点全部复制尾插在新链表后面。

                注意点:需要把新链表最后的节点的next置为空,因为可能会链接着需要删除的节点的地址。

        代码如下:

typedef struct ListNode SLTNode;
struct ListNode* removeElements(struct ListNode* head, int val) 
{//创建新链表SLTNode* new_head, * new_tail;new_head = new_tail = NULL;//遍历原链表SLTNode* pcur = head;while(pcur){if(pcur->val != val){if(new_head == NULL)//尾插分为两种情况:①链表为空 ②链表不为空new_head = new_tail = pcur;else{new_tail->next = pcur;new_tail = new_tail->next;}}pcur = pcur->next;}if(new_tail)new_tail->next = NULL;return new_head;
}

2、反转链表

        题目链接如下:

                https://leetcode.cn/problems/reverse-linked-list/description/

        题解思路:

                创建三个指针,第一个指针 p1 指向 nullptr,第二个指针指向头节点,第三个指针指向头节点的下一个节点。然后把第二个指针的指向改成第一个指针所指向的 nullptr,再把第二个指针的地址赋给第一个指针,第三个指针的地址赋给第二个指针,第三个指针向后走一步。

        注意点:

                需要判断第三个指针是否为空:因为在处理最后一个节点的指向的时候,第三个指针的指向已经是空了,所以要判断一下第三个指针是否为空,若为空就不能解引用结构体里面的值把next赋给自己了;

                循环的判断条件为第二个指针是否为空,因为当第二个指针为空的时候,就没有需要改变指向的节点了(画图理解)。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {if(head == NULL)return head;else{ListNode* p1, *p2, *p3;p1 = NULL, p2 = head, p3 = head->next;while(p2){p2->next = p1;p1 = p2;p2 = p3;if(p3)p3 = p3->next;}return p1;//p1为反转链表的头指针}
}

3、链表的中间节点

        题目链接如下:

                https://leetcode.cn/problems/middle-of-the-linked-list/description/

        解题思路:

                创建两个指针,起始位置都指向目标链表的第一个节点处;

                一个指针为慢指针,另一个指针为快指针,顾名思义慢指针的移动速度比快指针慢,慢指针走一步,快指针走两步;

                当快指针到达最后一个节点指向的空地址,或者快指针的下一个节点就是空地址的时候,慢指针所指向的节点就是中间节点。

                原理:假设快指针走的路程为n,快指针走的路程是慢指针的两倍,即 2*慢=快;此时慢指针走的路程就是n/2。当链表的长度为5的时候,快指针到达了终点5,而慢指针的路程为2(整形去除小数点),又因为起始位置为1,所以到达的节点是3,为中间节点;当链表的长度为6的时候,快指针到达了终点7,慢指针的路程为3,又因为起始位置为1,所以到达的节点是4,为中间节点。

        注意点:

                while判断的 pfast && pfast->next 的顺序不能换,若换过来判断偶数节点数中间节点的时候,pfast已经为空地址,空地址不能解引用 pfast->next,这样会报错。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {ListNode* pfast, *pslow;pfast = pslow = head;while(pfast && pfast->next){pfast = pfast->next->next;pslow = pslow->next;}return pslow;
}

4、合并两个有序的单链表

        题目链接如下:

                https://leetcode.cn/problems/merge-two-sorted-lists/description/

        解题思路如下:

                创建一个新的链表,创建两个指针分别指向两个有序的单链表;两个有序单链表的节点开始比较,较小或者相等的节点尾插到新链表中,然后比较时较小链表的指针与新链表中指向为节点的指针都向后走一步,开始循环比较大小;循环结束后若是有一方还存在数据,直接把数据全部插入到新链表中。

        注意点:

                因为每次比较之后都要判断新链表的头尾指针是否为空(为尾插分两种情况),显得代码冗余,可以直接动态申请一个内存给新链表的头尾指针,这样就可以不用检查判断是否为空了。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{if(list1 == NULL)return list2;//这里处理了list1为空的情况与list1和list2都为空的情况if(list2 == NULL)return list1;ListNode* new_head , *new_tail;//创建新链表new_head = new_tail = (ListNode*)malloc(sizeof(ListNode));ListNode* p1 = list1, *p2 = list2;//创建指针指向两个数组while(p1 && p2){if(p1->val < p2->val){new_tail->next = p1;new_tail = p1;p1 = p1->next;}else{new_tail->next = p2;new_tail = p2;p2 = p2->next;}}if(p1)new_tail->next = p1;if(p2)new_tail->next = p2;ListNode* node = new_head->next;//释放掉无有效数据的哨兵节点free(new_head);new_head = node;return new_head;
}

5、链表分割

        题目链接如下:

                https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70

        解题思路:

                创建两个非空链表,每个链表都要创建哨兵节点,这样不用每次尾插都要检查节点指针是否为空(因为单链表的尾插有两种情况);然后进行比较数据大小,把小于x的属于与大于或等于x的数据分别插入小链表与大链表;最后把大链表的节点插入小链表中,释放哨兵节点。

        注意点:

                尾插不会改变要插入节点的next,这里原来的节点的next可能还是下一个较小节点的地址,这样打印会造成死循环;大连表尾插进小链表后需要把大链表的最后一个节点的next置空。

        答案代码如下:

class Partition 
{
public:ListNode* partition(ListNode* pHead, int x) {//创建两个非空链表(创建哨兵节点,这样不用每次尾插都要检查节点指针是否为空)ListNode* less_head, * less_tail;less_head = less_tail = (ListNode*)malloc(sizeof(ListNode));ListNode* big_head, * big_tail;big_head = big_tail = (ListNode*)malloc(sizeof(ListNode));//比较数据大小,分别插入大链表与小链表ListNode* pcur = pHead;while(pcur){if(pcur->val < x){less_tail->next = pcur;less_tail = less_tail->next;}else {big_tail->next = pcur;big_tail = big_tail->next;}pcur = pcur->next;}//把大链表的节点插入小链表中//尾插不会改变要插入节点的next//这里原来的节点的next可能还是下一个较小节点的地址,这样打印会造成死循环,需要把大链表的最后一个节点的next置空big_tail->next = nullptr;less_tail->next = big_head->next;//释放哨兵节点ListNode* re = less_head->next;free(less_head);free(big_head);less_head = big_head = nullptr;return re;}
};

6、链表的回文结构

        题目链接如下:

                https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa

        解题思路:

                回文数字、字符串:轴对称;

                需要用到的思想为前面题目中的找中间节点与反转链表:设置快慢指针,找到中间节点;设置三个指针,分别指向nullptr、中间节点与中间节点的第二个指针,进行指向反转。最后把反转后的链表与中间节点之前的部分链表从头开始比较,若完全相等就是回文结构。

        答案代码如下:

class PalindromeList {
public:ListNode* fine_mid(ListNode* phead){ListNode* pslow, * pfast;pslow = pfast = phead;while(pfast && pfast->next){pslow = pslow->next;pfast = pfast->next->next;}return pslow;}ListNode* reversal(ListNode* mid){ListNode* p1, *p2, *p3;p1 = nullptr, p2 = mid, p3 = mid->next;while(p2){p2->next = p1;p1 = p2;p2 = p3;if(p3)p3 = p3->next;}return p1;}bool chkPalindrome(ListNode* A) {//一、找中间节点ListNode* mid_node = fine_mid(A);//二、把中间节点后面的数据都反转ListNode* rever_head = reversal(mid_node);//三、把反转之后的节点与原头节点while(rever_head)//循环条件是rever_head是否存在,因为rever_head指向的单链表的最后有nullptr,而A取不符合条件{if(rever_head->val != A->val)return false;rever_head = rever_head->next;A = A->next;}return true;}
};

7、相交链表

        题目链接如下:

                https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

        相交链表的图例如下:

        解题思路:

                首先从两个不同的头节点开始到尾进行遍历,并计算从不同头节点开始的总节点个数,并计算节点个数之差的绝对值;然后设置长链表与短链表,让长链表先走节点差数的步数,再开始分别遍历两个链表,若有相同地址的节点,则返回该节点的地址,若没有相同地址的节点,则返回nullptr。

        注意点:

                求绝对值函数:abs()。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {//遍历链表,找节点差ListNode* node_A = headA;ListNode* node_B = headB;int size_A = 0, size_B = 0;while(node_A){size_A++;node_A = node_A->next;}while(node_B){size_B++;node_B = node_B->next;}int gap = abs(size_A - size_B);//计算节点个数差//设置长短链表ListNode* long_list = headA;ListNode* short_list = headB;if(size_A < size_B){long_list = headB;short_list = headA;}while(gap--)//先让长链表走节点差步long_list = long_list->next;//遍历两链表,有相同地址值就返回节点地址,没有就返回nullwhile(long_list && short_list){if(long_list == short_list)return long_list;long_list = long_list->next;short_list = short_list->next;}return NULL;
}

8、环形链表 · 是否是环形链表

        题目链接如下:

                https://leetcode.cn/problems/linked-list-cycle/description/

        解题思路如下:

                带环链表:从头节点开始遍历链表,遍历不会结束。链表的尾节点next指针不指向空;

                使用的是快慢指针的思想(即慢指针一次走一步,快指针⼀次走两步),两个指针从链表起始位置开始运行,如果链表带环则⼀定会在环中相遇,否则快指针率先走到链表的未尾。

        注意点:

                无论快指针一次走多少步,最终都会与慢指针相遇。并且快指针走了三步及以上步数,就会引入额外的代码步骤,所以一般快指针都是走两步。

        答案代码如下:

typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {ListNode* fast = head, *slow = head;while(fast && fast->next){if(fast == slow)return true;fast = fast->next->next;slow = slow->next;} return false;
}

9、环形链表 · 寻找环形起始点

        题目链接如下:

                https://leetcode.cn/problems/linked-list-cycle-ii/description/

        解题思路:

                解题原理:让⼀个指针从链表起始位置开始遍历链表,同时让⼀个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。

                具体过程:设置快慢指针从环形链表的头节点开始遍历,慢指针走一步,快指针走两步,得到他们的相遇点并记录下来;然后设置两个速度相同的指针,一个从环形链表的头节点开始遍历,另一个从快慢指针的相遇点开始遍历,他们每次都是走一步;最终会在环形链表的循环起始点相遇。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) 
{ListNode* fast = head;ListNode* slow = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast){ListNode* node = head;while(node != slow){node = node->next;slow = slow->next;}return slow;}}return NULL;
}

10、随机链表的复制

        题目链接如下:

                https://leetcode.cn/problems/copy-list-with-random-pointer/description/

        解题思路:

                原链表如下:

                ① 复制旧链表中的val值来创建新节点,插入到旧链表头节点之后的每个节点之间:

                ② 根据旧链表的 random 指针的关系来赋值给新节点的 random 指针:

                node->random = pcur->random->next; 若pcur->random指向空,直接让pcur->random指向空。

                ③ 断开新旧节点之间的联系。

        答案代码如下:

typedef struct Node node;node* Buy_node(int num)
{node* re = (node*)malloc(sizeof(node));re->val = num;re->next = re->random = NULL;return re;
}void Add_node(node* phead)
{node* phead_after = NULL;node* new_node = NULL;while(phead){new_node = Buy_node(phead->val);phead_after = phead->next; new_node->next = phead_after;phead->next = new_node;phead = phead_after;    }
}struct Node* copyRandomList(struct Node* head) 
{//处理传空指针的情况if(head == NULL)return NULL;//一、创建节点,并尾插在每个原节点的后面Add_node(head);//二、处理新节点的random指针node* pcur = head;node* copy = pcur->next;while(pcur->next->next)//这里条件结束后不能处理最后一个节点{if(pcur->random != NULL)copy->random = pcur->random->next;pcur = copy->next;copy = pcur->next;}if(pcur->random != NULL)copy->random = pcur->random->next;//三、断开新链表与旧链表的联系pcur = head;node* new_head = pcur->next, * new_tail = pcur->next;while(pcur->next->next){pcur = pcur->next->next;new_tail->next = pcur->next;new_tail = new_tail->next;}return new_head;
}

二、链表的分类

        链表的结构非常多样,以下情况组合起来就有八种链表结构:

        每种情况具体如下:

        

        虽然有这么多的链表结构,但实际中最常用的还是两种结构:单链表双向链表

        单链表的全称是:单向不带头不循环链表;

        双向链表的全称是:双向带头循环链表。

        双向链表与单链表完全不是同一类。

三、双向链表

(一)概念与结构

        带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里“放哨的”。

        双向链表的节点结构:数据 + 指向后一个节点的指针 + 指向前一个节点的指针。

        双向链表比单链表多的东西:哨兵节点 + 指向前一个节点的指针 + 双向循环。

(二)双向链表的实现

双向链表的编写:

① 头文件:定义双向链表的节点结构体,声明要提供的操作(起到目录作用);

② cpp文件:编写具体实现各种操作(起到内容作用);

③ 测试文件:测试是否可以正常运行。

List.h

#pragma once#include<stdlib.h>
#include<iostream>
#include<stdbool.h>
#include<assert.h>
using namespace std;typedef int LTDataType;
typedef struct LTNode
{LTDataType data;struct LTNode* prev;struct LTNode* next;}LTNode;//一、节点的创建并初始化、打印链表、判断头节点、查找节点、销毁链表
//(一)节点的创建并初始化
LTNode* Buy_node(LTDataType num);
//(二)链表的初始化
LTNode* LTInit();
//(三)打印链表
void LTPrint(LTNode* phead);
//(四)判断是否只有头节点
bool LTEmpty(LTNode* phead);
//(五)查找节点
LTNode* LTFine(LTNode* phead, LTDataType num);
//(六)销毁链表(是指整个链表都销毁,包括哨兵尾,会影响头指针)
void LTDestroy(LTNode*& phead);//二、节点的插入
//(一)尾插
void LTPushBack(LTNode* phead, LTDataType num);
//(二)头插
void LTPushFront(LTNode* phead, LTDataType num);
//(三)指定位置插入节点
void LTInsert(LTNode* pos, LTDataType num);
//(四)指定位置之后插入节点
void LTInsertAfter(LTNode* pos, LTDataType num);//三、节点的删除
//(一)尾删
void LTPopBack(LTNode* phead);
//(二)头删
void LTPopFront(LTNode* phead);
//(三)指定位置删除节点
void LTErase(LTNode*& pos);
//(四)指定位置之后删除节点
void LTEraseAfter(LTNode* pos);

List.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"//一、节点的创建、初始化、打印链表、判断头节点、查找节点、销毁链表
//(一)节点的创建并初始化
LTNode* Buy_node(LTDataType num)
{//节点的创建LTNode* new_node = (LTNode*)malloc(sizeof(LTNode));if (new_node == nullptr){perror("Buy_node malloc fail");exit(1);}//节点的初始化new_node->data = num;new_node->next = new_node->prev = new_node;return new_node;
}
//(二)链表的初始化
LTNode* LTInit()
{LTNode* plist = Buy_node(-1);return plist;
}
//(三)打印链表
void LTPrint(LTNode* phead) 
{LTNode* pcur = phead->next;cout << "-> ";while (pcur != phead){cout << pcur->data << " -> ";pcur = pcur->next;}cout << endl;
}
//(四)判断是否只有头节点
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}
//(五)查找节点
LTNode* LTFine(LTNode* phead, LTDataType num)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == num)return pcur;pcur = pcur->next;}return nullptr;
}
//(六)销毁链表
void LTDestroy(LTNode*& phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next_noed = pcur->next;//next_noed->prev = phead;//phead->next = next_noed;free(pcur);pcur = next_noed;}free(phead);pcur = phead = nullptr;
}//二、节点的插入
//(一)尾插
void LTPushBack(LTNode* phead, LTDataType num)
{LTNode* new_node = Buy_node(num);new_node->next = phead;new_node->prev = phead->prev;phead->prev->next = new_node;phead->prev = new_node;}
//(二)头插
void LTPushFront(LTNode* phead, LTDataType num)
{LTNode* new_node = Buy_node(num);new_node->next = phead->next;new_node->prev = phead;phead->next->prev = new_node;phead->next = new_node;}
//(三)指定位置插入节点
void LTInsert(LTNode* pos, LTDataType num)
{assert(pos);LTNode* new_node = Buy_node(num);LTNode* pre_node = pos->prev;new_node->next = pos;new_node->prev = pre_node;pos->prev = new_node;pre_node->next = new_node;}
//(四)指定位置之后插入节点
void LTInsertAfter(LTNode* pos, LTDataType num)
{assert(pos);LTNode* new_node = Buy_node(num);LTNode* after_node = pos->next;new_node->next = after_node;new_node->prev = pos;after_node->prev = new_node;pos->next = new_node;
}//三、节点的删除
//(一)尾删
void LTPopBack(LTNode* phead)
{assert(phead);//空指针不可删除,需要判断assert(!LTEmpty(phead));//判断是不是只有头节点LTNode* del = phead->prev;LTNode* pre = del->prev;pre->next = phead;phead->prev = pre;free(del);del = nullptr;
}
//(二)头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTEmpty(phead));LTNode* del = phead->next;LTNode* del_after = del->next;del_after->prev = phead;phead->next = del_after;free(del);del = nullptr;
}
//(三)指定位置删除节点
void LTErase(LTNode*& pos)
{assert(pos);LTNode* pre_node = pos->prev;LTNode* after_node = pos->next;pre_node->next = after_node;after_node->prev = pre_node;free(pos);pos = nullptr;
}
//(四)指定位置之后删除节点
void LTEraseAfter(LTNode* pos)
{assert(pos);LTNode* del = pos->next;LTNode* after_node = del->next;after_node->prev = pos;pos->next = after_node;free(del);del = nullptr;}
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"void test()
{LTNode* plist = Buy_node(-1);//LTPrint(plist);for (int i = 1; i < 11; i++)LTPushBack(plist, i);//LTPushFront(plist, i);LTPrint(plist);/*for (int i = 0; i < 5; i++)LTPopFront(plist);LTPrint(plist);*///LTNode* re_pos =  LTFine(plist, 10);//LTInsertAfter(re_pos, 255);//LTEraseAfter(re_pos);LTDestroy(plist);//LTPrint(plist);
}int main()
{ test();return 0;
}

        总结:

                ① 第一个节点指的是第一个有效的节点,而哨兵位是无效的节点(头节点);

                ② 插入时修改节点的顺序:先修改插入的新节点,再修改前一个指针(head->prev )和head指针的指向;(先改新节点,然后从后向前改 )

                ③ 在哨兵位之前插入,也是尾插;

四、顺序表与链表的分析


        以上内容仅供分享,若有错误,请多指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/423969.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3+ant design vue实现可编辑表格弹出气泡弹出窗~

1、这里主要是介绍下::v-deep伪元素的作用。用于穿透组件作用域&#xff0c;以便在组件内部修改样式。用来覆盖Ant Design Vue组件库中的样式 <a-table:dataSource"dataList":columns"columns":scroll"{ x: 100% }":pagination"false&q…

react-intl——react国际化使用方案

国际化介绍 i18n&#xff1a;internationalization 国家化简称&#xff0c;首字母首尾字母间隔的字母个数尾字母&#xff0c;类似的还有 k8s(Kubernetes) <br /> React-intl是 React 中最受欢迎的库。 使用步骤 安装 # use npm npm install react-intl -D # use yarn项目…

6.6高斯噪声

在OpenCV联合C中给一张图片添加高斯噪声&#xff08;Gaussian Noise&#xff09;&#xff0c;可以通过生成随机数并在图像的每个像素上加上这些随机数来实现。高斯噪声是一种统计分布服从正态分布的噪声&#xff0c;通常用于模拟自然界的许多物理现象。 示例代码 以下是一个使…

云曦2024秋季开学考

ezezssrf 第一关&#xff1a;md5弱比较 yunxi%5B%5D1&wlgf%5B%5D2 第二关&#xff1a; md5强比较 需要在bp中传参&#xff0c;在hackbar里不行 yunxiiM%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DC V%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_B…

华为HCIA、HCIP和HCIE认证考试明细

华为认证体系包括三个主要等级&#xff1a;HCIA&#xff08;华为认证ICT助理&#xff09;、HCIP&#xff08;华为认证ICT高级工程师&#xff09;和HCIE&#xff08;华为认证ICT专家&#xff09;。每个等级的认证都有其特定的考试内容和费用。 HCIA&#xff08;华为认证ICT助理…

Excel 基础知识-操作手册2

十、查找与引用函数 Excel中的查找与引用函数非常丰富&#xff0c;以下是一些主要的函数及其使用示例&#xff1a; 1. **VLOOKUP** - 语法&#xff1a;VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup]) - 示例&#xff1a;假设A列是员工编号&#xff0c;B…

国际快递跟集运有什么区别?怎么做才能好集运?

在国际物流的舞台上&#xff0c;海外集运和国际快递是两种备受瞩目的运输方式&#xff0c;那两者之间有什么区别呢&#xff1f; 国际快递其实类似于国内快递&#xff0c;只是运输终点是海外。一般由公司或个人直接向海外邮寄&#xff0c;采用飞机运输&#xff0c;3 - 5 天就能…

IntelliJ IDE 插件开发 | (十二)自定义项目脚手架(上)

系列文章 本系列文章已收录到专栏&#xff0c;交流群号&#xff1a;689220994&#xff0c;也可点击链接加入。 前言 在开发创建一个新项目的时候&#xff0c;我们一般都会使用平台自带的脚手架&#xff0c;如下图所示&#xff1a; 或者是使用网页版&#xff1a; 尽管平台已经…

先楫HPM6750 Windows下VSCode开发环境配置

用的是EVKmini&#xff0c;ft2232作为调试器jtag接口调试 启动start_gui.exe 以hello_world为例&#xff0c;更改一下build path&#xff0c;可以generate并使用gcc compile 最后会得到这些 点击start_gui里面的命令行&#xff0c;用命令行启动vscode 新建.vscode文件夹&…

如何让Windows控制台窗口不接受鼠标点击(禁用鼠标输入)

一、简述 在我们编写控制台应用程序时&#xff0c;默认情况下程序的打印输出会在控制台窗口中进行显示&#xff0c;我们在写服务功能时在窗口中会不断打印消息输出&#xff0c;这个时候如果使用鼠标点击了控制台窗口&#xff0c;会阻塞程序的继续运行&#xff0c;导致我们的程…

vue使用TreeSelect设置带所有父级节点的回显

Element Plus的el-tree-select组件 思路&#xff1a; 选中节点时&#xff0c;给选中的节点赋值 pathLabel&#xff0c;pathLabel 为函数生成的节点名字拼接&#xff0c;数据源中不包含。 在el-tree-select组件中设置 props“{ label: ‘pathLabel’ }” 控制选中时input框中回…

树莓派智能语音助手实现音乐播放

树莓派语音助手从诞生的第一天开始&#xff0c;我就想着让它能像小爱音箱一样&#xff0c;可以语音控制播放音乐。经过这些日子的倒腾&#xff0c;今天终于实现了。 接下里&#xff0c;和大家分享下我的实现方法&#xff1a;首先音乐播放模块用的是我在上一篇博文写的《用sound…

基于spring的博客系统(二)

4. 业务代码 4.1 持久层 根据需求, 先⼤致计算有哪些DB相关操作, 完成持久层初步代码, 后续再根据业务需求进⾏完善 1. ⽤⼾登录⻚ a. 根据⽤⼾名查询⽤⼾信息 2. 博客列表⻚ a. 根据id查询user信息 b. 获取所有博客列表 3. 博客详情⻚ a. 根据博客ID查询博客信息 b. 根据博客I…

现代 Web 开发工具箱:Element-UI 表单组件全攻略(二)

现代 Web 开发工具箱&#xff1a;Element-UI 表单组件全攻略&#xff08;二&#xff09; 一 . Switch 开关控件1.1 Switch 组件的创建① 注册路由② 创建 Switch 组件 1.2 Switch 组件的属性① 开关的宽度② 开关 打开/关闭 的文字提示③ 开关打开或者关闭时候的值④ 开关打开或…

Qt控制开发板的LED

Qt控制开发板的LED 使用开发板的IO接口进行控制是嵌入式中非常重要的一点&#xff0c;就像冯诺依曼原理说的一样&#xff0c;一个计算机最起码要有输入输出吧&#xff0c;我们有了信息的接收和处理&#xff0c;那我们就要有输出。 我们在开发板上一般都是使用开发板的GPIO接口…

测试通用面试题大全

24年软件测试的发展如何&#xff1f; 1、IT行业还会继续升温&#xff0c;高质量人才需求相对还是短缺。 2、要求变高之后&#xff0c;很难再下降了&#xff0c;学历和经验。 3、功能测试之外的东西&#xff0c;接口、性能和自动化要掌握一点。 4、长远来看&#xff0c;软件…

数据集 wider_face 人脸数据集 人脸检测 >> DataBall

数据集 wider 人脸检测数据集 WIDER FACE: A Face Detection Benchmark inproceedings{yang2016wider, Author {Yang, Shuo and Luo, Ping and Loy, Chen Change and Tang, Xiaoou}, Booktitle {IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, Title…

Radware Alteon 负载均衡-基于URL Filetype的七层负载均衡

作者&#xff1a;Xiaolei Ren Radware Alteon作为一款高性能的负载均衡器&#xff0c;其基于URL Filetype的七层负载均衡功能为众多企业提供了灵活、高效的解决方案。 该案例实现如下需求&#xff1a;当客户端访问服务器时&#xff0c;默认访问10.200.1.100&#xff0c;在ht…

【Ubuntu】Ubuntu双网卡配置 实现内外网互不影响同时可用

【Ubuntu】Ubuntu双网卡配置 实现内外网互不影响同时可用 建议前提配置用到的命令参考文献&#xff1a; 建议 本文仅作个人记录&#xff0c;请勿完全照搬&#xff0c;建议直接看此视频&#xff0c;按作者的步骤进行配置 linux配置内外网&#xff08;ubuntu举例&#xff09;&am…

决策树算法上篇

决策树概述 决策树是属于有监督机器学习的一种&#xff0c;起源非常早&#xff0c;符合直觉并且非常直观&#xff0c;模仿人类做决策的过程&#xff0c;早期人工智能模型中有很多应用&#xff0c;现在更多的是使用基于决策树的一些集成学习的算法。 示例一&#xff1a; 上表根据…