目录
概述
链表(Linked List),运用结构体指针,可以实现将连续数组变为分段储存(内存上无须连续),比array更加灵活。
建立一个储存节点的结构体:
struct Node {
int data;
Node* next;//指向下一个节点,最后一个节点用NULL
};
Array基本要给一个确定的大小,链表就不会有这个问题。如果需要扩充储存,array可以在原有长度的基础上增加(前提原先长度之后的内存并没有占用),否则电脑会找一段足够长的内存,将原先的数据粘贴过去,再把新数据记录;链表会相对简单些,只需要增加一个节点。不过添加、删除、寻找元素会比较麻烦,毕竟链表的内存不连续,需要从头开始一个一个查找。
具体代码
1.建立一个指向头部的指针:
//C++
Node* head = NULL;
//C
struct Node* head = NULL;
//C用结构体指针都要记得加struct
因为一直要用到head,可以直接声明成全局变量;也可以在main()里声明,记得传参到函数里就行。
2.插入节点:
写一个Insert()函数,会方便很多。
因为是要在函数里完成插入,节点需要储存在堆(heap)里,因为函数运行完后会自动释放内存,如果结构体声明成local value,会在函数运行完后被自动释放(栈里运行完会自动释放)。所以记得用动态内存啊!
C里的3种分配和释放内存:
(malloc和calloc申请内存,realloc调整大小)这里使用malloc()和free()即可。
//例如:
struct Node* one = (struct Node*)malloc(sizeof(struct Node));
//(struct Node*):声明data type,因为malloc返回void
free(one);
C++中使用new 和 delete()。
Node* one = new Node();
delete(one);
(不要混用,像malloc用delete,new用free。new和delete会调用构造函数和析构函数,而malloc和free不会。malloc+delete可能会出错,new+free会没释放内存。)
-
头部插入:
将原先的首个节点的地址储存到新节点的next中,然后head指向新节点。
void InsertHead(int data) {
Node* tmp = new Node();//struct Node* tmp = (struct Node*)malloc(sizeof(struct Node));
tmp->data = data;
tmp->next = NULL;
if (head == NULL) {
head = tmp;//指向首个节点
return;
}
tmp->next = head;//新节点指向原头部节点
head = tmp;//更新head
}
' ->'用来访问结构体里的数据,tmp->data等于(*tmp).data, '.'也是访问符
(C里写Node*,前面都要加struct,which means:struct Node* tmp = ...)
-
尾部插入:
先遍历到尾部(指针->next == NULL),然后将最后一个节点的NULL变成新节点的地址。
void Insert(int num) {
Node* tmp = new Node();//struct Node* tmp = (struct Node*)malloc(sizeof(struct Node));
tmp->data = num;
tmp->next = NULL;
if (head == NULL) {
head = tmp;
return;
}
Node* tmp1 = head;
while (tmp1->next != NULL) {
tmp1 = tmp1->next;
}//运行完后,tmp1指向最后一个节点
tmp1->next = tmp;
}
//用C写记得一定要带struct!
-
随机位置插入:
插到开头位置和前面一样。插到中间的话,先遍历到插入位置的前一个节点。将下一节点的地址(储存在前一节点的next里)储存到新节点里,再将前一节点指向新节点。
void Insert(int data, int position) {
Node* tmp1 = new Node();//struct Node* tmp1 = (struct Node*)malloc(sizeof(struct Node));
tmp1->data = data;
tmp1->next = NULL;
if (position == 1) {
tmp1->next = head;
head = tmp1;
return;
}
Node* tmp2 = head;
for (int i = 0; i < position - 2; i++) {//head直接指向第一个位置,需要减1;还要遍历到前一节点,再减1
tmp2 = tmp2->next;
}
tmp1->next = tmp2->next;
tmp2->next = tmp1;
}
3.删除节点:
删除头部节点:更新head,删除节点。
非头部:将上一个节点的next链接到下一个节点上,删除节点。
void Delete(int x) {//删除data x
Node* tmp = head;
Node* prev = NULL;
while (tmp != NULL) {
if (tmp->data == x) {
if (x == head->data) {
head = tmp->next;
}//删除头部需要更新head
if (prev != NULL) {
prev->next = tmp->next;
}//非头部节点建立联结
delete(tmp);
return;
}
prev = tmp;
tmp = tmp->next;
}
}
4.反转链表:
- 创建三个指针,前、中、后,一个一个修改next。
//遍历
void Reverse() {
Node* prev, * current, * next;
prev = NULL;
current = head;
while (current != NULL) {
//next先指到下一个,修改current指向前一个
next = current->next;
current->next = prev;
//prev走到下一个,current走到下一个
prev = current;
current = next;
}
head = prev;
}
- 利用递归
//递归
void Reverse(Node* p) {
if (p->next == NULL) {
head = p;
return;
}
Reverse(p->next);
//Node* q = p->next;
//q->next = p;
//or
p->next->next = p;//修改下一节点的next,指向本节点
p->next = NULL;
}
5.打印链表:
- 正序打印,遍历一遍
void Print() {
Node* tmp = head;
while (tmp != NULL) {
cout << tmp->data << " ";
tmp = tmp->next;
}
cout << endl;
}
- 反向打印,用递归
void ReversePrint(Node* ptr) {
if (ptr == NULL) {
return;
}
ReversePrint(ptr->next);
cout << ptr->data << " ";
}
//main调用的时候把head传进来