线性表——单链表
单链表概念
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链式存储不仅需要存储元素本身,还要存储一个指向其后继元素的地址,这种存储结构也被称为 node,存储数据的叫数据域,存储地址的叫指针域。
单链表定义
链式存储结构需要存放数据的数据域和存放后继节点地址的指针域,所以在定义单链表时需要如下定义
typedef struct Node {
int data;
Node* next;
}ListNode, LinkList;
单链表的创建
单链表的创建从新插入的元素位置的不同分为头插法和尾插法
头插法创建单链表
头插法创建单链表即新插入的元素每次都会作为头指针 head 的后继节点,这样创建单链表时,每次插入新元素只需要修改 head 的指针,再让新节点的 next 指向原 head 指向的节点便可
具体的过程为:先为 head 节点和即将作为下一节点的 p 节点分配内存,再为 p 节点的数据域赋值,将 head 原本的 next 指针的值赋值给 p 的 next,再让 head 的 next 指向 p,这样就实现了在 head 后和上一次插入的节点前插入新节点的操作,使用 while 循环循环执行为 p 分配存储空间及其之后的代码,就实现了头插法创建单链表的过程
ListNode *Creat_LinkList() {
//头插法建立单链表
int x;
int index = 1;
ListNode *head, *p;
head = (ListNode*)malloc(sizeof(ListNode));
if (head == NULL) {
return head;
}
head->next = NULL;
cout << "输入要录入的数以0结尾" << endl;
cin >> x;
index++;
while (x != 0) {
p = (ListNode*)malloc(sizeof(ListNode));
if (p == NULL) {
return head;
}
p->data = x;
p->next = head->next;
head->next = p;
cout << "第% " << index << " %次输入:" << endl;
cin >> x;
index++;
}
return head;
}
尾插法创建单链表
尾插法创建单链表实际上就是头插法的反向方法,即每次新插入的节点都会被上一次插入的节点的 next 所指向,节点会按照输入的顺序一个一个添加在上一个结点之后
具体的过程为:先为头节点分配存储空间,然后将头节点的地址赋值给尾节点,这样在没进行其他操作之前,可以说头节点和尾节点是相同的,然后定义 p 节点并为其赋值,令当前的尾节点(第一次循环时尾节点就是头节点,之后的每次循环尾节点都会进行后移,所以每次插入新节点都可以想象成在头节点之后的最新节点和尾节点前添加)指向新节点,再将新节点赋值给尾节点,这里就是尾节点的后移,再令尾节点的 next 指向空,循环这部分的操作就实现了尾插法创建单链表
ListNode *Creat_LinkList_R() {
//尾插法建立单链表
int x;
ListNode *head, *p, *tail;
head = (ListNode*)malloc(sizeof(ListNode));
if (head == NULL)
return head;
head->next = NULL;
tail = head;
cout << "输入要录入的数以0结尾" << endl;
cin >> x;
while (x != 0) {
p = (ListNode*)malloc(sizeof(ListNode));
if (p == NULL)
return head;
p->data = x;
tail->next = p;
tail = p;
tail->next = NULL;
cin >> x;
}
return head;
}
###打印单链表
打印单链表的操作十分简单,传入头节点地址,定义一个当前节点位置的 p 节点,通过循环让 p 不停后移来获取每个节点中存放的数据
int Print_List(ListNode *head) {
int index = 1;
ListNode *p = head->next;
if (p == NULL) {
return 0;
}
while (p != NULL) {
cout << "第% " << index << " %个元素是:" << p->data << endl;
p = p->next;
index++;
}
return 1;
}
###获取单链表长度
int Length_List(ListNode *head) {
int num = 0;
ListNode *p = head;
while (p->next != NULL) {
num++;
p = p->next;
}
return num;
}
###按照索引查找元素
原理同获取单链表长度的原理接近,先通过计数器 num 将当前节点 p 移动到索引 i 的位置,再获取当前节点 p 中存放的数据
ListNode *GetD_index(ListNode *head, int i) {
//按照索引查找链表元素
ListNode *p = head;
int num = 0;
if (i < 0) {
return NULL;
}
while ((p->next != NULL) && (num < i)) {
num++;
p = p->next;
}
if (num == i) {
cout << "查找的元素是:" << p->data << endl;
return p;
}
else
return NULL;
}
###按照元素值查找元素
通过元素值查找链表中的数据也是需要通过当前节点 p 的不断后移,不停的取出每一个节点中的数据与目标值进行比较,直到找到值或者移动到了链表的结束
int GetD_data(ListNode *head, int data) {
//按照元素值查找链表元素
ListNode *p = head;
int index = 0;
while (p != NULL) {
if (p->data == data) {
cout << "查询到该元素,位置为% " << index << endl;
return 1;
}
else {
p = p->next;
index++;
}
}
return 0;
}
###删除后继节点
先令 r = p->next,再让 p->next 指向 r->next,这样就已经将 p 的后继节点从链表中摘除了,最后释放 r 的资源就完成了后继节点的删除操作
int DeleteAfter_LinkList(ListNode *p) {
//删除后继节点
ListNode *r;
if (!p)
return 0;
r = p->next;
if (!r)
return 0;
p->next = r->next;
free(r);
return 1;
}
总结
(一)从存储结构上来说,顺序表存放在一串连续的地址中,而链表的每个节点的存储位置都是随机的,所以对链表进行插入、删除操作时的时间复杂度为 O(1),但是查找时需要 O(n),相反的,顺序表需要平均遍历一半的数据才能完成相同的插入、删除操作,复杂度为 O(n),但是查找却为 O(1)
(二)从存储空间上说,顺序表需要预先分配好全部的存储空间,这样会造成资源的浪费或者不足等问题,而链表可以随用随分配,便捷的增删操作可以快速的为新节点分配存储空间