
链表是构建其他复杂数据结构的基础,如栈、队列、图和哈希表等。通过对链表进行适当的扩展和修改,可以实现这些数据结构的功能。想学算法,数据结构,不会链表是万万不行的。这篇笔记是一名小白在学习时整理的。
C语言
链表部分
链表的定义
这里用原始的方法定义了一个含有3个节点的链表,并用原始的办法输出。
输出时,一种用" -> ",另一种只用" . "
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int date;
struct Node* next;
}Node;
int main() {
Node node01 = { 11,NULL };
Node node02 = { 22,NULL };
Node node03 = { 33,NULL };
node01.next = &node02;
node02.next = &node03;
printf("%d\n", node01.date);
printf("%d\n", node01.next->date);
printf("%d\n", (*(node01.next)).date);
printf("%d\n", node01.next->next->date);
printf("%d\n", (*(*(node01.next)).next).date);
system("pause");
}
链表的遍历
循环遍历
以循环遍历的方式输出链表的数据
这里需要用到指针
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int date;
struct Node* next;
}Node;
int main() {
Node node01 = { 11,NULL };
Node node02 = { 22,NULL };
Node node03 = { 33,NULL };
node01.next = &node02;
node02.next = &node03;
Node* point = &node01;
while (point != NULL) {
printf("%d\n", point->date);
point = point->next;
}
system("pause");
}
递归遍历
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int date;
struct Node* next;
}Node;
void printList(Node* point);
int main() {
Node node01 = { 11,NULL };
Node node02 = { 22,NULL };
Node node03 = { 33,NULL };
node01.next = &node02;
node02.next = &node03;
Node* point = &node01;
printList(point);
system("pause");
}
void printList(Node* point) {
if(point != NULL){
printf("%d\n", point->date);
printList(point->next);
}
}
链表的插入
后插法
将一个新节点添加在date为22的节点之后
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int date;
struct Node* next;
}Node;
void printList(Node* point);
void insertListBehind(Node* point, int id, Node* newnode);
int main() {
Node node01 = { 11,NULL };
Node node02 = { 22,NULL };
Node node03 = { 33,NULL };
node01.next = &node02;
node02.next = &node03;
Node* point = &node01;
Node newnode = { 23,NULL };
insertListBehind(point, 22, &newnode);
printList(point);
system("pause");
}
void insertListBehind(Node* point, int id,Node*newnode) {
while (point != NULL) {
if (point->date == id) {
newnode->next = point->next;
point->next = newnode;
}
point = point->next;
}
}
void printList(Node* point) {
if(point != NULL){
printf("%d\n", point->date);
printList(point->next);
}
}
前插法
比后插难一点点,要用到头指针。
要考虑到前插到第一个节点的情况(单独考虑),我们可以用后插法来实现前插法。
即,如果前插到data为22的前面,我们可以转化为后插到data为22的节点的前一个节点的后面。
用到了二级指针
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
void printList(Node* head);
void insertListFront(Node** head, int id, Node* newnode);
int main() {
Node node01 = { 11, NULL };
Node node02 = { 22, NULL };
Node node03 = { 33, NULL };
node01.next = &node02;
node02.next = &node03;
Node* head = &node01;
Node newnode = { 12, NULL };
insertListFront(&head, 22, &newnode);
printList(head);
system("pause");
return 0;
}
void insertListFront(Node** head, int id, Node* newnode) {
Node* point = *head;
if (point->data == id) {
newnode->next = point;
*head = newnode;
}
else {
while (point->next != NULL) {
if (point->next->data == id) {
newnode->next = point->next;
point->next = newnode;
break;
}
point = point->next;
}
}
}
void printList(Node* head) {
Node* point = head;
while (point != NULL) {
printf("%d\n", point->data);
point = point->next;
}
}
链表的删除
可以参照前插法的思路,几乎一样的
这里删除data为22的那个节点。
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
void printList(Node* head);
void deleteListFront(Node** head, int id);
int main() {
Node node01 = { 11, NULL };
Node node02 = { 22, NULL };
Node node03 = { 33, NULL };
node01.next = &node02;
node02.next = &node03;
Node* head = &node01;
deleteListFront(&head, 22);
printList(head);
system("pause");
return 0;
}
void deleteListFront(Node** head, int id) {
Node* point = *head;
if (point->data == id) {
*head = point->next;
}
else {
while (point->next != NULL) {
if (point->next->data == id) {
point->next = point->next->next;
break;
}
point = point->next;
}
}
}
void printList(Node* head) {
Node* point = head;
while (point != NULL) {
printf("%d\n", point->data);
point = point->next;
}
}
注意:这里的删除仅仅是断开了链表与该块数据的链接,并不是真正的删除。
真正的删除在下面《动态链表节点的删除》
动态链表
链表的动态添加
于链表前端添加节点
类似前插法,但是用malloc,实际上更简单一点。
我们可以利用头指针来添加
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
}Node;
void addNodeFront(Node** head, int data);
void printList(Node* head);
int main() {
Node* head = NULL;
addNodeFront(&head, 111);
addNodeFront(&head, 222);
addNodeFront(&head, 333);
printList(head);
}
void addNodeFront(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
}
else {
newNode->next = *head;
*head = newNode;
}
}
void printList(Node* head) {
Node* point = head;
while (point != NULL) {
printf("%d\n", point->data);
point = point->next;
}
}
于链表后端添加节点
比前加难一点。因为头指针只指头,所以要找到尾指针。
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
}Node;
void addNodeBehind(Node** head, int data);
void printList(Node* head);
int main() {
Node* head = NULL;
addNodeBehind(&head, 111);
addNodeBehind(&head, 222);
addNodeBehind(&head, 333);
printList(head);
}
void addNodeBehind(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
}
else {
Node* point = *head;
while(point->next != NULL) {
point = point->next;
}
point->next = newNode;
}
}
void printList(Node* head) {
Node* point = head;
while (point != NULL) {
printf("%d\n", point->data);
point = point->next;
}
}
动态链表删除节点
由于C语言不会像Java那样有垃圾处理器,断开连接后内存不会自动释放。
我们可以使用 free() 来实现。free掉从链表上取下的节点。
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
}Node;
void deleteNode(Node** head, int id);
void addNodeBehind(Node** head, int data);
void printList(Node* head);
int main() {
Node* head = NULL;
addNodeBehind(&head, 111);
addNodeBehind(&head, 222);
addNodeBehind(&head, 333);
deleteNode(&head, 222);
printList(head);
}
void deleteNode(Node** head, int id) {
Node* point = *head;
if (point->data == id) {
*head = point->next;
free(point);
}
else {
while (point->next != NULL) {
if (point->next->data == id) {
Node* deletetemp = point->next;
point->next = deletetemp->next;
free(deletetemp);
}
point = point->next;
}
}
}
void addNodeBehind(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
}
else {
Node* point = *head;
while(point->next != NULL) {
point = point->next;
}
point->next = newNode;
}
}
void printList(Node* head) {
Node* point = head;
while (point != NULL) {
printf("%d\n", point->data);
point = point->next;
}
}
只需要在原来删除节点的基础上,标记出要删除的那个节点,在断开链接后,再free掉它。
循环列表
之前学习研究的链表都是条状的,非循环的连表,有头有尾。循环列表是环状的哦。
循环列表的插入及遍历
如果循环列表为空,即插入的节点为第一个节点,该节点的next应指向自身。若不为空,先找到原列表最后一个,添加后,让新的末端指向头。
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
}Node;
void addNodeBehind(Node** head, int data);
void printList(Node* head);
int main() {
Node* head = NULL;
addNodeBehind(&head, 111);
addNodeBehind(&head, 222);
addNodeBehind(&head, 333);
printList(head);
}
void addNodeBehind(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
}
else {
Node* point = *head;
while(point->next != *head) {
point = point->next;
}
point->next = newNode;
}
newNode->next = *head;
}
void printList(Node* head) {
Node* point = head;
do{
printf("%d\n", point->data);
point = point->next;
}
while (point != head);
}
注意:因为链表的结束标志不再是节点的next指向为空,而是指向为头结点,所以遍历时应做出修改。因为point一开始就指向头节点,结束标志也是头节点,所以我使用do-while循环。
双向链表
大多为环装
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
struct Node* prev;
}Node;
void addNode(Node**head,int data);
void printList(Node* head);
int main() {
Node* head = NULL;
addNode(&head, 111);
addNode(&head, 222);
addNode(&head, 333);
printList(head);
return 0;
}
void addNode(Node** head, int data) {
Node* newnode = (Node*)malloc(sizeof(Node));
newnode->data = data;
newnode->next = newnode;
newnode->prev = newnode;
if (*head == NULL) {
*head = newnode;
}
else {
Node* first = *head;
Node* last = first->prev;
last->next = newnode;
newnode->prev = last;
newnode->next = first;
first->prev = newnode;
}
}
void printList(Node* head) {
Node* point = head;
do {
printf("%d\n", point->data);
point = point->next;
}
while (point != head);
}
相比之下,双向链表插入等操作时,要断开2个链接,建立2个链接。但是不必使用循环。因为可以直接通过头找到尾。
C++语言
C++ List的基础知识
和<vector>的用法很像。
但是不能通过索引(索引的实质是地址的偏移量)来查找,因为链表的内存不是连续的,不像数组那样。不能随机查找
一些list函数,关键字等将在代码中进行注释
list的的声明
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1; // 创建一个空的链表
std::list<int> list2(3, 100); // 创建一个包含3个值为100的元素的链表
std::list<int> list3(l2); // 创建一个l2的副本
return 0;
}
list元素的访问和修改
访问函数
front()
:返回链表中第一个元素的引用back()
:返回链表中最后一个元素的引用
修改函数
push_front()
:在链表头部插入一个元素pop_front()
:移除链表头部的元素push_back()
:在链表尾部插入一个元素pop_back()
:移除链表尾部的元素insert()
:在指定位置插入一个元素- remove():删除指定的元素
clear()
:移除链表中的所有元素- unique():除重
cpp
#include<iostream>
#include<list>
using namespace std;
int main() {
list<int> List;
List.push_back(111);
List.push_back(222);
List.push_back(333);
List.push_front(444);
List.pop_back();
List.pop_front();
for (auto i : List) {
cout << i << " ";
}
return 0;
}
输出结果为111 222
list的基础操作
操作函数
merge()
:将另一个有序链表other
合并到当前链表中,合并后链表仍然有序sort()
:对链表中的元素进行排序reverse()
:反转链表中元素的顺序
容量函数
empty()
:判断链表是否为空size()
:返回链表中元素的数量
cpp
#include<iostream>
#include<list>
using namespace std;
int main() {
list<int> List;
List.push_back(111);
List.push_back(222);
List.push_back(333);
cout << "List size: " << List.size() << endl;
while (!List.empty()) {
cout << List.front() << endl;
List.pop_front();
}
cout << List.empty() << endl;
cout << "List size: " << List.size() << endl;
return 0;
}
C++List的迭代器
迭代器相关函数
begin()
:返回指向链表第一个元素的迭代器end()
:返回指向链表末尾(最后一个元素之后)的迭代器rbegin()
:返回指向链表最后一个元素的反向迭代器rend()
:返回指向链表开头(第一个元素之前)的反向迭代器
begin()和end()的使用示例
cpp
#include<iostream>
#include<list>
using namespace std;
int main() {
list<int> List;
List.push_back(111);
List.push_back(222);
List.push_back(333);
cout << *List.begin() << endl;
cout << *--List.end() << endl;
return 0;
}
注意:.end并不指向最后一个节点,其指向为空,用--list.end可以获得链表List的最后的节点的地址。
List迭代器的使用
list< >::iterator __
cpp
Vlist<int>::iterator it = List.begin();
创建一个名字叫 it 的迭代器,他指向名字叫 List 的链表的第一个节点
advance( , )
cpp
advance(it, 2);
将名字叫 it 的迭代器正向移动2个节点。
这里是输出第3到第4的节点的数据
cpp
#include<iostream>
#include<list>
using namespace std;
int main() {
list<int> List;
List.push_back(000);
List.push_back(111);
List.push_back(222);
List.push_back(333);
List.push_back(444);
list<int>::iterator it = List.begin();
advance(it, 2);
for (int i = 0; i < 2; i++) {
cout << *it << endl;
it++;
}
return 0;
}