1.双向链表的实现
1.1双向链表节点的结构声明
typedef int LTDataType;typedef struct ListNode
{struct ListNode* prev; // 指向该节点的前一个节点struct ListNode* next; // 指向该节点的后一个节点LTDataType data; // 该节点中存储的数据
}LTNode; // 将这个结构体重新命名为LTNode
1.2链表的初始化
LTNode* ListInit()
{// 创建一个节点,这个节点是哨兵位LTNode* guard = (LTNode*)malloc(sizeof(LTNode));if (guard == NULL){perror("malloc fail");exit(-1);}// 这是一个带头双向循环的链表// 此时只有一个哨兵位,因此哨兵位的前一个节点和后一个节点都是哨兵位本身guard->next = guard;guard->prev = guard;return guard;
}
1.3 链表的尾插
// 创建一个新节点
LTNode* BuyListNode(LTDataType x)
{// 创建一个新节点LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("malloc fail");exit(-1);}// 初始化这个节点的指针,和数据node->next = NULL;node->prev = NULL;node->data = x;return node;
}
// 尾插
void ListPushBack(LTNode* phead, LTDataType x)
{assert(phead);// 尾插一个数据前,创建一个新节点,将这个数据存储到这个节点中LTNode* newnode = BuyListNode(x);// phead就是头节点,因为是双向循环链表,所以phead->prev就是双向循环链表的尾节点LTNode* tail = phead->prev; // 尾插一个新节点,就是将下面三个节点,按照顺序进行连接// tail newnode phead // 1.尾节点指向新节点tail->next = newnode; // 2.新节点的前指针,指向尾节点newnode->prev = tail;// 3.新节点的后指针,指向头节点newnode->next = phead;// 4.头节点的前指针,指向新节点,新节点成为这个双向循环链表的新的尾节点phead->prev = newnode;
}
1.4链表的打印
void ListPrint(LTNode* phead)
{assert(phead);printf("phead<=>");//不打印哨兵位的头// phead就是哨兵位,不存储数据,因此不需要进行打印LTNode* cur = phead->next; // cur等于phead,说明已经循环了一周,所有的数据都已经被打印了while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}
1.5链表的头插
- 方法一
- 方法二
// 因为双向循环链表是有哨兵位的,头插的节点是插入到哨兵位之后的
// 也就是将 head newnode d1 这三个节点,按照顺序连接就可以
void ListPushFront(LTNode* phead, LTDataType x)
{assert(phead);// 方法一:// 需要关心节点连接的循序(如果先执行第二步,那么phead指向的就是新节点,而不是d1节点)// 创建一个要插入的新节点,存储的数据为xLTNode* newnode = BuyListNode(x);// 第一步// phead->next就是d1节点// 新节点的后指针指向d1节点newnode->next = phead->next;// phead->next->prev 就是d1节点的前指针;也就是d1节点的前指针指向新节点phead->next->prev = newnode;// 第二步// 哨兵位的后指针指向新节点phead->next = newnode;// 新节点的前指针指向哨兵位newnode->prev = phead;// 方法二:/*// 不关心顺序(第一步和第二步哪个先执行都可以)LTNode* newnode = BuyListNode(x);// 1.先保存d1节点的地址LTNode* first = phead->next;// 第一步:// 哨兵位的后指针指向新节点phead->next = newnode;// 新节点的前指针指向哨兵位newnode->prev = phead;// 第二步:// 新节点的后指针指向d1节点newnode->next = first;// d1节点的前指针指向新节点first->prev = newnode;*/// 方法三:// 在phead->next位置前插入,也就是头插// void ListInsert(LTNode* pos, LTDataType x) 这个函数就是在pos位置前插入一个节点// ListInsert(phead->next, x);
}
1.6链表的尾删
// 用来判断是否为空链表,空链表无法头删
bool ListEmpty(LTNode* phead)
{assert(phead);// 方法一(比较麻烦)/*if (phead->next == phead)return true;elsereturn false;*/// 如果相等,那么就是空链表(因为这是一个双向循环链表)return phead->next == phead;
}
void ListPopBack(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));// 哨兵位的前指针指向的就是双向循环链表的尾节点LTNode* tail = phead->prev;// 尾节点的前指针指向的前节点将其命名为prevLTNode* prev = tail->prev;// 要进行尾删,也就是将 phead tail prev 三个节点中的tail删除,再将phead和prev连接// 1.连接phead和prevprev->next = phead;phead->prev = prev;// 释放要删除的节点空间,将其变为空指针,防止产生野指针free(tail);tail = NULL;
}
1.7链表的头删
// 对于双向循环链表,头删就是将 phead d1 d2中的d1节点删除,再将phead和d2节点连接
void ListPopFront(LTNode* phead)
{assert(phead);// 保证这个双向循环链表不是空链表assert(!ListEmpty(phead));// phead->next就是d1节点LTNode* first = phead->next;// phead->next->next就是d2节点LTNode* second = first->next;// 连接phead和d2节点phead->next = second;second->prev = phead;// 释放d1节点free(first);first = NULL;
}
1.8链表节点的数量
size_t ListSize(LTNode* phead)
{assert(phead);size_t n = 0;LTNode* cur = phead->next;// 当cur != phead,说明链表循环一周结束while (cur != phead){++n;cur = cur->next;}return n;
}
1.9链表的查找
LTNode* ListFind(LTNode* phead, LTDataType x)
{assert(phead);size_t n = 0;// 哨兵位没有存储数据,不需要进行查找LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}// 迭代cur = cur->next;}return NULL;
}
1.10在pos位置之前插入
// 在pos之前插入
// 也就是将prev newnode pos进行连接
void ListInsert(LTNode* pos, LTDataType x)
{assert(pos);// 将pos前指针指向的节点,命名为prevLTNode* prev = pos->prev;// 创建一个新节点LTNode* newnode = BuyListNode(x);// 连接3个节点 prev newnode pos;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}
头插
void ListPushFront(LTNode* phead, LTDataType x)
{assert(phead);//在phead->next之前插入,也就是头插ListInsert(phead->next, x);
}
尾插
void ListPushBack(LTNode* phead, LTDataType x)
{assert(phead);//在phead之前插入,也就是尾插ListInsert(phead, x);
}
1.11删除pos位置
void ListErase(LTNode* pos)
{assert(pos);// prev pos nextLTNode* prev = pos->prev;LTNode* next = pos->next;// 连接prev和next,释放pos节点prev->next = next;next->prev = prev;free(pos);//需要调用的人自行置空,因为pos是传值拷贝,在这里将pos置空,主函数中的pos并没有被改变//pos = NULL;
}
尾删
void ListPopBack(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));//删除phead->prev也就是尾删ListErase(phead->prev);
}
头删
void ListPopFront(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));// 删除phead->next也就是头删ListErase(phead->next);
}
1.12销毁链表
void ListDestory(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;// 释放当前节点free(cur);// 迭代cur = next;}// 释放哨兵位free(phead);// 可以传二级,内部置空头结点// 建议:也可以考虑用一级指针,让调用ListDestory的人置空 (保持接口一致性)// phead = NULL;
}
2.双向循环链表的完整实现
- List.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>// 双链表的节点的结构体
typedef int LTDataType;
typedef struct ListNode
{struct ListNode* next;struct ListNode* prev;LTDataType data;
}LTNode;// 初始化一个双向循环链表
LTNode* ListInit();
// 销毁这个双向循环链表
void ListDestory(LTNode* phead);// 打印链表
void ListPrint(LTNode* phead);// 尾插
void ListPushBack(LTNode* phead, LTDataType x);// 头插
void ListPushFront(LTNode* phead, LTDataType x);// 尾删
void ListPopBack(LTNode* phead);// 头删
void ListPopFront(LTNode* phead);// 判断链表是否为空
bool ListEmpty(LTNode* phead);// 链表的节点数量
size_t ListSize(LTNode* phead);// 查找指定节点
LTNode* ListFind(LTNode* phead, LTDataType x);// pos位置前插入节点
void ListInsert(LTNode* pos, LTDataType x);
// 删除pos位置的节点
void ListErase(LTNode* pos);
- List.c
#include "List.h"// 初始化,并返回哨兵位的地址
LTNode* ListInit()
{LTNode* guard = (LTNode*)malloc(sizeof(LTNode));if (guard == NULL){perror("malloc fail");exit(-1);}guard->next = guard;guard->prev = guard;return guard;
}// 创建一个新节点
LTNode* BuyListNode(LTDataType x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("malloc fail");exit(-1);}node->next = NULL;node->prev = NULL;node->data = x;return node;
}// 打印链表中的数据
void ListPrint(LTNode* phead)
{assert(phead);printf("phead<=>");LTNode* cur = phead->next;while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}void ListPushBack(LTNode* phead, LTDataType x)
{assert(phead);ListInsert(phead, x);
}void ListPushFront(LTNode* phead, LTDataType x)
{assert(phead);ListInsert(phead->next, x);
}void ListPopBack(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListErase(phead->prev);
}void ListPopFront(LTNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListErase(phead->next);
}bool ListEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
}size_t ListSize(LTNode* phead)
{assert(phead);size_t n = 0;LTNode* cur = phead->next;while (cur != phead){++n;cur = cur->next;}return n;
}LTNode* ListFind(LTNode* phead, LTDataType x)
{assert(phead);size_t n = 0;LTNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}// 在pos之前插入
void ListInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* prev = pos->prev;LTNode* newnode = BuyListNode(x);// prev newnode pos;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}// 删除pos位置
void ListErase(LTNode* pos)
{assert(pos);LTNode* prev = pos->prev;LTNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);//pos = NULL;
}// 可以传二级,内部置空头结点
// 建议:也可以考虑用一级指针,让调用ListDestory的人置空 (保持接口一致性)
void ListDestory(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
- main.c
#define _CRT_SECURE_NO_WARNINGS#include "List.h"void TestList1()
{// 初始化链表LTNode* plist = ListInit();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);ListPushFront(plist, 10);ListPushFront(plist, 20);ListPushFront(plist, 30);ListPushFront(plist, 40);ListPrint(plist);ListPopBack(plist);ListPopBack(plist);ListPopBack(plist);ListPopBack(plist);ListPrint(plist);ListPopBack(plist);ListPopBack(plist);ListPopBack(plist);ListPopBack(plist);ListPrint(plist);// 防止内存泄漏ListDestory(plist);plist = NULL;
}void TestList2()
{LTNode* plist = ListInit();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);ListPopFront(plist);ListPopFront(plist);ListPrint(plist);ListPopFront(plist);ListPopFront(plist);ListPrint(plist);ListDestory(plist);plist = NULL;
}void TestList3()
{LTNode* plist = ListInit();ListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPushBack(plist, 4);ListPrint(plist);// ...
}int main()
{TestList2();return 0;
}
3.顺序表和链表的区别