一、单链表:
【前提,本文用的所有代码都是抄的,我懒得写,所以只是对代码的解释,勿喷,如果有错可以告诉我,如果侵权,我可以删掉代码,嘿嘿嘿】
链表:还用多说吗,肯定就单链表、双链表、循环链表。
每种链表又分为带头节点和不带头节点的,为啥要区分带不带头节点,因为带头节点的会方便很多,无论后续的增、删、改、查都会简单很多,后续会说明。
因为前面一篇顺序表太简单了,所以我就没敲代码上来,链表对于初学者会难理解一点点,所以我还是拿代码进行说明吧,不过我会抄一下其他人的代码,主要起一个说明作用,方便自己理解,也方便有人看我这篇文章理解。
0、定义:
// 定义链表的节点
typedef struct node
{
int data; //这个节点要保存什么数据
struct node *next; //这个节点指向下一个节点
}Node,*Link; //定义别名 Node, *Link
看见上面这个代码蒙蔽不,上面这个就是对单链表的节点的定义,我们来对比一下顺序表的定义:
//定义顺序表
typedef struct
{
int length;//当前顺序表长度
int Maxsize;//顺序表最大长度
int* data;//定义顺序表中元素类型的数组指针
}SqList;
是不是发现两个特别像,但是又不一样,废话,都是用结构体定义的,肯定像。区别也有,顺序表中的这个指针是单纯的指针,也就是并不是自己定义的结构体指针。换句话说就是,一个是普通指针,一个是结构体指针。你要问我这个指针有什么用,那我们来看单链表的初始化和顺序表的初始化。(为什么我扯上顺序表,一方面补一下上一篇的代码,一方面可以看一下两者的比较,至于我后面讲循环链表和双链表的时候就是在单链表上做延伸,不再做比较了)
1、初始化:
链表初始化:
// 带头结点的单链表初始化函数
ListNode* initListWithHead() {
// 分配头结点空间
ListNode *head = (ListNode*)malloc(sizeof(ListNode));
if (head == NULL) {
printf("Memory allocation failed!\n");
exit(1);
}
// 初始化头结点
head->val = 0; // 头结点不存储数据,可以设置为任意值
head->next = NULL;
return head;
}
// 不带头结点的单链表初始化函数
ListNode* initListWithoutHead() {
// 初始时链表为空,返回NULL
return NULL;
}
顺序表初始化:
void InitList(SqList &L)
{
L.data = (int *)malloc(InitSize*sizeof(int));//用malloc函数申请一片空间放我表中的数据
L.length = 0;//顺序表中现在没有值,那不就是空表咯,不是0还能是什么
L.Maxsize = InitSize;//最大长度不定义的话,鬼知道我能插入多少数据进去
}
两者的比较都写在注释里了,可以自己看,看得懂就看,看不懂就看书。
2、插入:
(包含带头节点和不带头节点的头插法、尾插法,再补一下昨天的顺序表插入)
插入操作:
链表不同与顺序表,顺序表就顺着插就好了,链表有头插法和尾插法,啥叫头插法,每次插入的新元素都在头节点之后的第一个元素,就叫头插,啥叫尾插,每次插入的新元素都在尾巴,就交尾插。
带头节点的尾插法代码:
struct node* insert_node(struct node* head, int pos, int data)//head是我传入的头节点,pos是表长,data是我要插入的数据
{
NODE* p = head, * q = head;
NODE* new_node = (NODE*)malloc(sizeof(NODE));
new_node->data = data;
for (int i = 1; i <= pos; i++)//循环节束后q指向表尾,p指向空
{
q = p;
p = p->next;
}
if (p == NULL)
{
p->new_node;
q->next = p;
new_node->next = NULL;
}
return head;
}
带头节点的头插法代码:
NODE* insert_node_at_head(NODE* head, int data) {
NODE* new_node = (NODE*)malloc(sizeof(NODE));
if (new_node == NULL) {
//分配失败,那岂不是没节点 ,这里写报错代码
}
new_node->data = data;
new_node->next = head->next; // 将新节点的next指向原头节点的后一个节点
head->next = new_node; // 更新头节点为新节点
return head; // 返回新的头节点
}
不带头节点的尾插:
// 尾插法
Node* insertAtTail(Node* head, int data) {
Node* newNode = createNode(data); //这里创建节点的函数就没用malloc函数,其实都差不多,自己想怎么改就怎么改,毕竟代码是自己写的
if (head == NULL) {
// 如果链表为空,新创建的节点就是首元节点
head = newNode;
} else {
// 否则,找到尾节点并插入新节点
Node* current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
return head; // 返回首元节点
}
不带头节点的头插:
// 头插法
Node* insertAtHead(Node* head, int data) {
if (head == NULL) {
// 如果链表为空,新创建的节点就是首元节点
head = newNode;
} else {
Node* newNode = createNode(data);
newNode->next = head; // 新节点的next指向原首元节点
head = newNode; // 更新首元节点为新节点
}
return head; // 返回新的首元节点
}
以上就是带头节点和不带头节点的两种代码比较,相同点:都是要传入头节点,尾插法可以传入表长直接然后用for循环找到尾节点,也可以不传入表长直接while找到,代码不一致,这里也告诉大家,不用拘泥于代码写法,能实现目的的写法就是最好的写法
接下来就是顺序表的插入:
bool ListInsret(SqList &L)
{
int i, e;
printf("请输入要插入顺序表的元素和元素位置:");
scanf("%d %d", &e, &i);
if (i<1 || i>L.length + 1)//判断元素下标是否越界
return false;
if (L.length > L.Maxsize)//判断顺序表存储空间是否满了
return false;
for (int j = L.length; j >= i; j--)
{
L.data[j] = L.data[j-1];//从后往前逐个后移元素
}
L.data[i-1] = e;//将新元素放入下标为i-1的位置
L.length++;//表长+1
printf("插入的元素是%d,插入的位置是%d\n", e, i);
return true;
}
因为顺序表的实质是一个数组,所以有下标索引,插入一个元素都会涉及到元素的迁移(每次都往最后一个元素的后一个位置插入例外哈,别杠精)正因为有下标索引,所以查询方便,插入删除不方便。
3、查找:
(链表按值查找返回地址,顺序表按值查找返回下标):
// 链表取值查询
int getValueWithHead(Node* head, int e) {
if (head == NULL || position < 1) {
printf("Invalid head or position!\n");
return -1; // 都没有头节点你找个锤子
}
Node* current = head->next; // 跳过头节点 //(如果是不带头节点的就不后面的箭头和next去掉)
int index = 1;
while (current != NULL) {
if (current->data==e) {
return current; // 找到指定位置,返回指针,也就是地址
}
current = current->next;
}
return -1; // 说明超出范围都找不到这个值
}
// 顺序表查询
void LocateElem(SqList &L)
{
int e;
int k = 1;
printf("输入你要查找的元素:");
scanf("%d", &e);
for (int i = 0; i < L.length; i++)
if (L.data[i] == e)
{
printf("找到了,是第%d个元素\n", i + 1);
k = 0;
break;
}
if (k)
printf("找不到元素%d\n", e);
}
4、删除:
删除肯定要考虑带头节点和不带头节点,比如带头节点的,是不是删到只剩头节点就算删完了,但是不带头节点的要删除到空才行,顺序表的删除,能在定义的时候就知道表长,表长为0就是删除节束。接下来看代码:
// 带头节点的链表删除操作
void deleteWithHead(Node** head, int position) {
if (*head == NULL || position < 1) {
printf("Invalid head or position!\n");
return;
}
Node* current = (*head)->next; // 跳过头节点
Node* prev = *head;
int index = 1;
// 查找要删除的节点的前一个节点
while (current != NULL && index < position) {
prev = current;
current = current->next;
index++;
}
// 如果current为空,说明位置超出范围
if (current == NULL) {
printf("Position out of range!\n");
return;
}
// 删除节点
prev->next = current->next;
free(current);
}
// 不带头节点的链表删除操作
void deleteWithoutHead(Node** head, int position) {
if (*head == NULL || position <= 0) {
printf("Invalid head or position!\n");
return;
}
Node* current = *head;
Node* prev = NULL;
int index = 1;
// 查找要删除的节点的前一个节点
while (current != NULL && index < position) {
prev = current;
current = current->next;
index++;
}
// 如果current为空,说明位置超出范围
if (current == NULL) {
printf("Position out of range!\n");
return;
}
// 删除节点
if (prev == NULL) {
// 如果要删除的是头节点
*head = current->next;
} else {
prev->next = current->next;
}
free(current);
}
//顺序表删除
bool ListDelete(SqList &L)
{
int i, e;
printf("请输入要删除的元素位置:");
scanf("%d",&i);
if (i<1 || i>L.length + 1)//判断元素下标是否越界
return false;
if (!L.data)//判断是不是空表
{
printf("空表\n");
return false;
}
e = L.data[i - 1];
for (int j = i; j <= L.length; j++)
{
L.data[j-1] = L.data[j];
}
L.length--;//表长-1
printf("删除的元素是%d,这个元素的位置是%d\n", e, i);
return true;
}
看得懂插入,删除还会远吗,所以,就这样吧,慢慢比较吧。
二、循环单链表:
循环链表我只在单链表上做延伸,代码我也会附上去,但是不会再比较顺序表了,只是对于带头节点的和不带头节点的进行比较
0、定义:
// 定义节点结构体
typedef struct Node {
int data;
struct Node *next;
} Node;
// 定义循环单链表结构体
typedef struct CircularLinkedList {
Node *head; // 指向头节点的指针
} CircularLinkedList;
// 定义不带头节点的循环单链表节点结构体
typedef struct CircularLinkedListWithoutHead {
int data;
struct CircularLinkedListWithoutHead *next;
} CircularLinkedListWithoutHead;
上面的定义自行比较...
1、初始化:
// 初始化循环单链表
void InitCircularLinkedList(CircularLinkedList *list) {
list->head = (Node*)malloc(sizeof(Node));
if (!list->head) {
exit(EXIT_FAILURE);
}
list->head->next = list->head; // 初始化为循环链表
}
// 初始化不带头节点的循环单链表
void InitCircularLinkedListWithoutHead(CircularLinkedListWithoutHead **list) {
*list = NULL;
}
上面的初始化自行比较...
2、插入:
// 插入节点到循环单链表
void InsertNode(CircularLinkedList *list, int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
if (!newNode) {
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = list->head->next;
list->head->next = newNode;
}
// 插入节点到不带头节点的循环单链表
void InsertNodeWithoutHead(CircularLinkedListWithoutHead **list, int data) {
CircularLinkedListWithoutHead *newNode = (CircularLinkedListWithoutHead*)malloc(sizeof(CircularLinkedListWithoutHead));
if (!newNode) {
exit(EXIT_FAILURE);
}
newNode->data = data;
if (*list == NULL) {
newNode->next = newNode; // 只有一个节点,指向自己
} else {
CircularLinkedListWithoutHead *current = *list;
while (current->next != *list) {
current = current->next;
}
newNode->next = *list;
current->next = newNode;
}
*list = newNode; // 更新头指针
}
上面的插入自行比较...
3、删除
// 删除节点从循环单链表
void DeleteNode(CircularLinkedList *list, int data) {
Node *current = list->head->next;
Node *prev = list->head;
while (current != list->head && current->data != data) {
prev = current;
current = current->next;
}
if (current == list->head) {
// 未找到要删除的节点
printf("Node with data %d not found.\n", data);
return;
}
prev->next = current->next;
free(current);
}
// 删除节点从不带头节点循环单链表
void DeleteNodeWithoutHead(CircularLinkedListWithoutHead **list, int data) {
if (*list == NULL) {
// 链表为空
printf("List is empty.\n");
return;
}
CircularLinkedListWithoutHead *current = *list;
CircularLinkedListWithoutHead *prev = NULL;
// 查找要删除的节点
while (current->data != data && current->next != *list) {
prev = current;
current = current->next;
}
// 如果没有找到要删除的节点
if (current->data != data) {
printf("Node with data %d not found.\n", data);
return;
}
// 如果删除的是唯一节点
if (prev == NULL && current->next == *list) {
free(*list);
*list = NULL;
}
// 如果删除的是头节点
else if (prev == NULL) {
CircularLinkedListWithoutHead *temp = *list;
*list = current->next;
free(temp);
}
// 如果删除的是中间或尾节点
else {
prev->next = current->next;
free(current);
}
// 如果链表变为空,需要特殊处理
if (*list == NULL) {
return;
}
// 恢复循环链表的尾部指向头节点
current = *list;
while (current->next != *list) {
current = current->next;
}
current->next = *list;
}
上面的删除自行比较...
4、查询
// 查询节点是否在循环单链表中
int SearchNode(CircularLinkedList *list, int data) {
Node *current = list->head->next;
while (current != list->head) {
if (current->data == data) {
return 1; // 找到节点
}
current = current->next;
}
return 0; // 未找到节点
}
// 查询节点是否在不带头节点的循环单链表中
int SearchNodeWithoutHead(CircularLinkedListWithoutHead *list, int data) {
if (list == NULL) {
return 0; // 链表为空
}
CircularLinkedListWithoutHead *current = list;
do {
if (current->data == data) {
return 1; // 找到节点
}
current = current->next;
} while (current != list);
return 0; // 未找到节点
}
上面的查询自行比较...
代码看完了,有没有发现一个问题,为啥不带头节点的代码相同的操作会比带头节点的代码长很多很多。当你发现这个问题的时候,就明白了,为啥人类宁愿浪费一个节点的空间也要做一个头节点出来了。因为方便省事啊...
三、循环双量表:
1、(直接gpt生成的一个带头节点的循环双链表)
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
struct Node *prev;
} Node;
typedef struct CircularDoubleLinkedList {
Node *head;
} CircularDoubleLinkedList;
// 初始化循环双链表
void initCircularDoubleLinkedList(CircularDoubleLinkedList *list) {
list->head = NULL;
}
// 插入节点到循环双链表
void insertNode(CircularDoubleLinkedList *list, int data) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = data;
if (list->head == NULL) {
newNode->next = newNode;
newNode->prev = newNode;
list->head = newNode;
} else {
Node *last = list->head->prev;
newNode->next = list->head;
list->head->prev = newNode;
newNode->prev = last;
last->next = newNode;
}
}
// 从循环双链表中删除节点
void deleteNode(CircularDoubleLinkedList *list, int key) {
if (list->head == NULL)
return;
Node *current = list->head;
Node *prev = NULL;
while (current->data != key) {
if (current->next == list->head) {
printf("Key not found in the list.\n");
return;
}
prev = current;
current = current->next;
}
if (current == list->head && current->next == list->head) {
list->head = NULL;
free(current);
return;
}
if (current == list->head) {
prev = list->head->prev;
list->head = list->head->next;
prev->next = list->head;
list->head->prev = prev;
free(current);
} else if (current->next == list->head) {
prev->next = list->head;
list->head->prev = prev;
free(current);
} else {
prev->next = current->next;
current->next->prev = prev;
free(current);
}
}
// 查询节点是否存在于循环双链表中
int searchNode(CircularDoubleLinkedList *list, int key) {
if (list->head == NULL)
return 0;
Node *current = list->head;
do {
if (current->data == key)
return 1;
current = current->next;
} while (current != list->head);
return 0;
}
2、(直接gpt生成的一个不带头节点的循环双链表)
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
struct Node *prev;
} Node;
typedef struct CircularDoubleLinkedList {
Node *head;
} CircularDoubleLinkedList;
// 初始化循环双链表
void initCircularDoubleLinkedList(CircularDoubleLinkedList *list) {
list->head = NULL;
}
// 插入节点到循环双链表
void insertNode(CircularDoubleLinkedList *list, int data) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = data;
if (list->head == NULL) {
newNode->next = newNode;
newNode->prev = newNode;
list->head = newNode;
} else {
Node *last = list->head->prev;
newNode->next = list->head;
list->head->prev = newNode;
newNode->prev = last;
last->next = newNode;
}
}
// 从循环双链表中删除节点
void deleteNode(CircularDoubleLinkedList *list, int key) {
if (list->head == NULL)
return;
Node *current = list->head;
Node *prev = NULL;
while (current->data != key) {
if (current->next == list->head) {
printf("Key not found in the list.\n");
return;
}
prev = current;
current = current->next;
}
if (current == list->head && current->next == list->head) {
list->head = NULL;
free(current);
return;
}
if (current == list->head) {
prev = list->head->prev;
list->head = list->head->next;
prev->next = list->head;
list->head->prev = prev;
free(current);
} else if (current->next == list->head) {
prev->next = list->head;
list->head->prev = prev;
free(current);
} else {
prev->next = current->next;
current->next->prev = prev;
free(current);
}
}
// 查询节点是否存在于循环双链表中
int searchNode(CircularDoubleLinkedList *list, int key) {
if (list->head == NULL)
return 0;
Node *current = list->head;
do {
if (current->data == key)
return 1;
current = current->next;
} while (current != list->head);
return 0;
}
因为复试在即,所以我就不自己敲代码了,过一遍代码而已,就偷懒抄别人的代码过来,或者gpt生产了一下,重在理解其中的代码逻辑和实现手法,因为代码毕竟还是自己写出来的比较顺畅,如果有喜欢的,我以后也会写一些奇奇怪怪的技术文章,其他的不重要,单纯就写写玩玩。大概率不会考虑收费,因为我学习的时候就喜欢白嫖,学到是我的本事,也是你的本事,就此这篇文章告辞,嘿嘿嘿,山高水长路还远,祝好运.....