目录
一、双向链表的实现
二、顺序表和带头双向循环链表的区别
愿你熬过万丈孤苦,藏下星辰大海。
带头、双向、循环
头部的prev指向尾部,
List.h
#pragma once #include#include #include typedef int LTDataType; typedef struct ListNode { LTDataType data; struct ListNode* next; struct ListNode* prev; }LTNode; void ListPrint(LTNode* phead); //void ListInit(LTNode** pplist);//初始化,二级指针 LTNode* ListInit(LTNode* phead); void ListPushBack(LTNode* phead, LTDataType x);//尾插 void ListPopBack(LTNode* phead);//尾删 void ListPushFront(LTNode* phead, LTDataType x); void ListPopFront(LTNode* phead); LTNode* ListFind(LTNode* phead, LTDataType x); //pos之前插入 void ListInsert(LTNode* pos, LTDataType x); //删除pos位置的节点 void ListErase(LTNode* pos); void ListDestory(LTNode* phead);
List.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "List.h" LTNode* BuyLTNode(LTDataType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode)); if (newnode == NULL) { printf("malloc fail\n"); exit(-1); } newnode->prev = NULL; newnode->data = x; newnode->next = NULL; } 初始化,二级指针的思路; //void ListInit(LTNode** pphead)//初始化,next指向自己,prev指向自己,才算是循环,头的地址还不清楚//初始化个哨兵位 //{ // assert(pphead); // *pphead = BuyLTNode(0); // (*pphead)->prev = *pphead; // (*pphead)->next = *pphead; //} //初始化,一级指针的思路,返回头即可,既可以改变头的地址,又可以传一级指针 LTNode* ListInit(LTNode* phead) { phead = BuyLTNode(0); phead->prev = phead; phead->next = phead; return phead; } //打印 void ListPrint(LTNode* phead) { //从head的next = cur->data开始打印,当cur->next = head结束 assert(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); /*LTNode* tail = phead->prev; LTNode* newnode = BuyLTNode(x); tail->next = newnode; newnode->prev = tail; newnode->next = phead; phead->prev = newnode;*/ } //尾删,尾删到没有节点,也不用处理 void ListPopBack(LTNode* phead) { assert(phead); //链表为空 assert(phead->next != phead); /*LTNode* tail = phead->prev; LTNode* tailPrev = tail->prev; phead->prev = tailPrev; tailPrev->next = phead; free(tail); tail = NULL;*/ ListErase(phead->prev); } //头插 void ListPushFront(LTNode* phead, LTDataType x) { assert(phead); ListInsert(phead->next, x); /*LTNode* newnode = BuyLTNode(x); LTNode* next = phead->next; next->prev = newnode; newnode->next = next; phead->next = newnode; newnode->prev = phead;*/ } //头删 void ListPopFront(LTNode* phead) { assert(phead); ListErase(phead->next); /*assert(phead->next != phead); LTNode* head = phead->next; LTNode* next = head->next; phead->next = next; next->prev = phead; free(head); head = NULL;*/ } //查找 LTNode* ListFind(LTNode* phead, LTDataType x) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; } //插入 void ListInsert(LTNode* pos, LTDataType x) { assert(pos); LTNode* newnode = BuyLTNode(x); LTNode* prev = pos->prev; prev->next = newnode; newnode->prev = prev; newnode->next = pos; pos->prev = newnode; } //删除 void ListErase(LTNode* pos)//在主函数中,pos的实参要置空,否则实参就会成为野指针 { assert(pos); LTNode* next = pos->next; LTNode* prev = pos->prev; prev->next = next; next->prev = prev; free(pos); pos = NULL; } //传一级指针是为了保持接口一致性 void ListDestory(LTNode* phead)//注意,phead的实参要进行free,否则会导致野指针 { assert(phead); LTNode* cur = phead->next; while (cur != phead) { LTNode* next = phead->next; free(cur); cur = next; } free(phead); phead = NULL; }
(1)首先进行哨兵位的初始化,哨兵位进行初始化之后,地址就不会再发生变化,所以在进行头插、尾插时,不需要传二级指针,仅仅需要哨兵位即可【尾插、头插也是在哨兵位之后】
(2)改变的是结构体的地址,改变的是指针的内容,改变指针的内容,需要传递指针的地址。
这两个结构是相辅相成的结构,
顺序表:优点:(1)物理空间是连续的,方便用下标随机访问。【二分查找、排序】
(2)CPU高速缓存命中率会更高。
缺点:(1)由于需要物理空间连续,空间不够需要扩容。扩容本身有一定的消耗,其次扩容机制还存在一定的空间消耗。(2)头部或者中间的插入删除,需要挪动数据,效率低。O(N)
链表:优点(1)按需申请释放空间(2)任意位置可以O(1)的插入删除数据
缺点:不支持下标的随机访问,有些算法不适合在他上面进行。如:二分查找、排序等
编译连接之后,生成可执行程序,CPU执行这个程序,CPU要去访问内存,CPU不会直接访问内存,CPU嫌弃内存速度太慢,会把数据加载到三级缓存或者寄存器。4或者8byte小数据放到寄存器,大数据放到缓存。CPU会看数据是否在缓存,在的话就命中,直接访问,不在的话,先把数据从内存加载到缓存,再访问。会缓存一段连续的空间,所以顺序表命中更高。