一个有趣的排列组合:
带头 | 不带头 |
循环 | 不循环 |
单向 | 双向 |
特点:
1.无头单向非循环链表: 结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构(如哈希桶、图的邻接表...),在笔试面试中出现很多。
2.带头双向循环链表: 结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单且实用。
具体实现:
声明
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
初始化
ListNode* InitList()
{
ListNode* phead = ListCreate(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
printf("哨兵位 ");
while (cur!=pHead)
{
printf("%d <-> ", cur->data);
cur = cur->next;
}
printf("\n");
return;
}
删除
头删
加入断言,防止没有元素了继续删除
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* cur = pHead->next;
pHead->next = pHead->next->next;
cur->next->prev = cur->prev;
free(cur);
cur = NULL;
}
尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->prev;
pHead->prev = tail->prev;
tail->prev->next = pHead;
free(tail);
tail = NULL;
return;
}
在指定位置删除
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* pre = pos->prev;
ListNode* next = pos->next;
pre->next = pos->next;
next->prev = pre;
free(pos);
pos = NULL;
}
插入
插入需要创建新结点,故直接封装一个函数用于实现这个功能
ListNode* ListCreate(x)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode == NULL)
{
perror("malloc fail");
}
newNode->data = x;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
ListNode* head = pHead;
ListNode* newNode = ListCreate(x);
newNode->next = pHead->next;
pHead->next->prev = newNode;
pHead->next = newNode;
newNode->prev = pHead;
}
尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* newNode = ListCreate(x);
tail->next = newNode;
newNode->prev = tail;
newNode->next = pHead;
pHead->prev = newNode;
}
在指定位置之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* pre= pos->prev;
ListNode* newNode = ListCreate(x);
newNode->next = pos;
pos->prev = newNode;
pre->next = newNode;
newNode->prev = pre;
}
查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
printf("没找到啊哦\n");
return NULL;
}
销毁
void ListDestory(ListNode** pHead)
{
assert(pHead);
ListNode* cur = (*pHead)->next;
while (cur!=*pHead)
{
ListNode* del = cur;
cur = cur->next;
free(del);
}
free(*pHead);
*pHead = NULL;
return;
}
全部代码
List.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
ListNode* InitList();
//创建
ListNode* ListCreate(x);
// 销毁
void ListDestory(ListNode** ppHead);
// 打印
void ListPrint(ListNode* pHead);
// 尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 尾删
void ListPopBack(ListNode* pHead);
// 头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 头删
void ListPopFront(ListNode* pHead);
// 查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 删除pos位置的节点
void ListErase(ListNode* pos);
List.c
#include"List.h"
ListNode* ListCreate(x)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode == NULL)
{
perror("malloc fail");
}
newNode->data = x;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
ListNode* InitList()
{
ListNode* phead = ListCreate(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* newNode = ListCreate(x);
tail->next = newNode;
newNode->prev = tail;
newNode->next = pHead;
pHead->prev = newNode;
}
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
printf("哨兵位 ");
while (cur!=pHead)
{
printf("%d <-> ", cur->data);
cur = cur->next;
}
printf("\n");
return;
}
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->prev;
pHead->prev = tail->prev;
tail->prev->next = pHead;
free(tail);
tail = NULL;
return;
}
void ListDestory(ListNode** pHead)
{
assert(pHead);
ListNode* cur = (*pHead)->next;
while (cur!=*pHead)
{
ListNode* del = cur;
cur = cur->next;
free(del);
}
free(*pHead);
*pHead = NULL;
return;
}
void ListPushFront(ListNode* pHead, LTDataType x)
{
ListNode* head = pHead;
ListNode* newNode = ListCreate(x);
newNode->next = pHead->next;
pHead->next->prev = newNode;
pHead->next = newNode;
newNode->prev = pHead;
}
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* cur = pHead->next;
pHead->next = pHead->next->next;
cur->next->prev = cur->prev;
free(cur);
cur = NULL;
}
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
printf("没找到啊哦\n");
return NULL;
}
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* pre= pos->prev;
ListNode* newNode = ListCreate(x);
newNode->next = pos;
pos->prev = newNode;
pre->next = newNode;
newNode->prev = pre;
}
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* pre = pos->prev;
ListNode* next = pos->next;
pre->next = pos->next;
next->prev = pre;
free(pos);
pos = NULL;
}
test.c
#include"List.h"
Test1()
{
ListNode* plist = InitList();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPrint(plist);
}
Test2()
{
ListNode* plist = InitList();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
}
Test3()
{
ListNode* plist = InitList();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPrint(plist);
ListDestory(&plist);
ListPrint(plist);
}
Test4()
{
ListNode* plist = InitList();
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
ListPushFront(plist, 5);
ListPrint(plist);
}
Test5()
{
ListNode* plist = InitList();
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
ListPushFront(plist, 5);
ListPrint(plist);
ListPopFront(plist);
ListPrint(plist);
ListPopFront(plist);
ListPrint(plist);
ListPopFront(plist);
ListPrint(plist);
ListPopFront(plist);
ListPrint(plist);
ListPopFront(plist);
ListPrint(plist);
ListPopFront(plist);
ListPrint(plist);
}
Test6()
{
ListNode* plist = InitList();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPrint(plist);
ListNode* pos = ListFind(plist, 5);
ListInsert(pos, 7);
ListPrint(plist);
}
Test7()
{
ListNode* plist = InitList();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPrint(plist);
ListNode* pos = ListFind(plist, 3);
ListErase(pos);
ListPrint(plist);
}
int main()
{
Test3();
return 0;
}
总结碎碎念(这个比那个容易头晕的二级指针好搞多了)
链表(双向)
优点
1、任意位置插入删除都是O(1)
2、按需申请释放,合理利用空间,不存在浪费问题缺点
下标随机访问不方便 O(N)
顺序表
优点
支持下标随机访问。O(1)缺点
1、头部或者中间插入删除效率低,要挪动数据。O(N)2、空间不够需要扩容,扩容有一定的消耗,且可能存在一定的空间浪费
3、只适合尾插尾删
tips:
计算机最重要的两部分:存储(内存、硬盘)和运算(CPU、GPU),其中,硬盘分为固态和磁盘,特点是不带电存储,读写速度相对慢,内存读写速度快,带电存储。
除此之外,还需要了解三级缓存和寄存器(他俩围绕在CPU旁边,充当一种介质:缓存)
CPU要访问内存,但是CPU觉得内存有点慢,就好比CPU执行指令打印数据,修改数据,但是内存读写速度还是太慢了(猫猫和狗狗决斗,狗狗<->内存,猫猫<->CPU),所以计算机设计了一种机制,把小的数据放到寄存器里,寄存器是最快的(但是小),加载到缓存中的是一长串数据(CPU读取成本一样),CPU是先去缓存看数据在不在(在:命中,直接访问;不在:不命中,将要访问的数据加载到缓存,一次加载一段数据到缓存--局部性原理)
所以链表带来的一个缺点就是:可能连续访问多次依旧不命中。
还有一个问题就是:缓存污染
缓存速度快,空间小,价格贵,空间不够会清出一些数据,可能会有一些不必要的数据加载到缓存中
一个表格概括:
不同点 | 顺序表 | 链表 |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持,O(1) | 不支持,O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低:O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |
膜拜大佬:
与程序员相关的CPU缓存知识 | 酷 壳 - CoolShell