三战双向带头循环链表

一个有趣的排列组合:

带头不带头
循环不循环
单向双向

 

 

 

 

特点:

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:

f9c13a9aeb8c4632b4820dca6e6e88fc.png

计算机最重要的两部分:存储(内存、硬盘)和运算(CPU、GPU),其中,硬盘分为固态和磁盘,特点是不带电存储,读写速度相对慢,内存读写速度快,带电存储。

除此之外,还需要了解三级缓存和寄存器(他俩围绕在CPU旁边,充当一种介质:缓存)

CPU要访问内存,但是CPU觉得内存有点慢,就好比CPU执行指令打印数据,修改数据,但是内存读写速度还是太慢了(猫猫和狗狗决斗,狗狗<->内存,猫猫<->CPU),所以计算机设计了一种机制,把小的数据放到寄存器里,寄存器是最快的(但是小),加载到缓存中的是一长串数据(CPU读取成本一样),CPU是先去缓存看数据在不在(在:命中,直接访问;不在:不命中,将要访问的数据加载到缓存,一次加载一段数据到缓存--局部性原理)

所以链表带来的一个缺点就是:可能连续访问多次依旧不命中。

还有一个问题就是:缓存污染

缓存速度快,空间小,价格贵,空间不够会清出一些数据,可能会有一些不必要的数据加载到缓存中

一个表格概括:

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持,O(1)不支持,O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低:O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

膜拜大佬:

与程序员相关的CPU缓存知识 | 酷 壳 - CoolShell

 

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值