双向链表-

链表特性:带头/不带头

                  循环/非循环

--->排列组合后,共有8种链表结构

一.双向链表的定义

前一个节点存了后一个节点的地址,后一个节点也存了前一个节点的地址,即循环链表

二.代码解析

//双向链表
//与非循环链表区别!该双向链表事先设置了一个哨兵位节点,所以不用传入二级指针,即可改变链表里面的内容
#include <iostream>
#include <stdlib.h>
#include <assert.h>
using namespace std;
//1.定义链表结构
typedef int LTDateType;//适用于多类型(eg:int)
typedef struct ListNode
{
	LTDateType data;//链表中储存的数据
    struct ListNode* prev;//指向节点的前一个节点
	struct ListNode* next;//指向节点的后一个节点
}LTNode;
//2.初始化链表
//法1,传入二级指针,同非循环链表,即void ListInit(LTNode**pphead);
//法2,设置哨兵位头节点
LTNode* ListInit()
{
	//设置哨兵位头节点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	//循环链表
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//3.打印链表
void ListPrint(LTNode* phead)
{
	//循环链表,从head的下一个节点开始打印
	assert(phead);//链表不能为空
	//head是哨兵位节点,是空的,所以不打印
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		cout << cur->data<<"->";
		cur = cur->next;
	}
	cout << endl;
}
//4.尾插
//已经创建了一个哨兵位头节点,所以尾插不需要传入二级指针
void ListPushBack(LTNode* phead,LTDateType x)
{
	//断言,链表不为空
	assert(phead);
	//创建一个新的节点
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->data=x;//在新创建的节点中插入数据
	//LTNode* newnode=BuyListNode(x);
	LTNode* tail = phead->prev;//定义一个变量指向链表的尾部
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
//5.尾删
void ListPopBack(LTNode* phead)
{
	//断言,链表不为空
	assert(phead);
	//防止删除掉创建的哨兵位节点
	assert(phead->next != phead);
	//法1
	//LTNode* tail = phead->prev;//指向链表的尾节点
	//tail->prev->next = phead;
	//phead->next = tail->prev;
	//free(tail);
	//法2,多定义几个变量
	LTNode* tail = phead->prev;//尾节点
	LTNode* tailPrev = tail->prev;//尾节点的前一个节点
	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;
}
LTNode* BuyListNode(LTDateType x)//用于创建新的节点
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->data = x;
	newnode->next = NULL;//滞空
	newnode->prev = NULL;//滞空
	return newnode;
}
//6.头插
void ListPushFront(LTNode* phead, LTDateType x)
{
	//注意插入的位置!
	//在哨兵位节点和第一个位置之间插入数据
	assert(phead);
	//由于每次都要检查扩展一个新的节点,所以设置一个函数专门用于扩展
	LTNode* newnode = BuyListNode(x);
	//创建好节点后,开始进行插入操作
	//找到哨兵位头节点后真正的第一个节点
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}
//7.头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	//防止将哨兵位节点删掉
	assert(phead->next != phead);
	//定义多个变量,增加程序的可读性
	LTNode* next = phead->next;//要删除的头部节点
	LTNode* nextNext = next->next;//头数据的下一个节点
	phead->next = nextNext;
	nextNext->prev = phead;
	//要记得将删除的头部节点的内存还给系统
	free(next);
}
//8.查找
//根据传进来的数字在链表中查找是否有相同的
LTNode* ListFind(LTNode* phead,LTDateType x)
{
	assert(phead);
	//从哨兵位节点的下一个节点开始查找
	LTNode* cur = phead->next;
	//限定范围,即何时停止查找
	while (cur!= phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	//未查找到
	return NULL;
}
//9.pos位置前插入
void ListInsert(LTNode* pos,LTDateType x)//可直接代替头插和尾差,即直接在这两个函数体中调用该函数
{
	assert(pos);
	//创建一个新节点
	LTNode* newnode = BuyListNode(x);
	//创建一个变量指向pos位置前的一个节点
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}
//10.删除pos位置
void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
	pos = NULL;
}
//11.销毁链表
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	//先释放除哨兵位头结点以外的节点,再单独销毁哨兵位头节点
	while (cur != phead)
	{
		LTNode* next = cur->next;//先保存下一个节点的地址,防止被置成随机值后找不到
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;//不能使plist清理干净,可在测试类中手动清理
}

每写一个功能,都要进行一次测试,以下为所有的测试类合集:

//测试类
void Test()
{
	//初始化
	LTNode* plist = ListInit();
	//尾插
	cout << "尾插测试:" << endl;
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	ListPrint(plist);
	cout << "---------------" << endl;
	//尾删
	cout << "尾删测试:" << endl;
	ListPopBack(plist);
	ListPopBack(plist);
	ListPrint(plist);
	cout << "---------------" << endl;
	cout << "头插测试:" << endl;
	ListPushFront(plist, 3);
	ListPushFront(plist,4);
	ListPushFront(plist, 5);
	ListPrint(plist);
	cout << "---------------" << endl;
	cout << "头删测试:" << endl;
	ListPopFront(plist);
	ListPrint(plist);
	cout << "---------------" << endl;
	cout << "查找测试:(修改数值)" << endl;
	LTNode* pos = ListFind(plist,4);
	//利用查找修改数值
	if (pos)
	{
		pos->data = 7;
	}
	ListPrint(plist);
	cout << "---------------" << endl;
	cout << "在pos位置前插入测试:" << endl;
	//配合查找,插入数据
	LTNode* search = ListFind(plist, 1);
	//即在1的前面插入节点
	ListInsert(search, 9);
	ListPrint(plist);
	cout << "---------------" << endl;
	cout << "删除pos位置节点测试:" << endl;
	LTNode* search2 = ListFind(plist, 2);
	ListErase(search2);
	ListPrint(plist);
	cout << "---------------" << endl;
	//销毁链表
	ListDestroy(plist);
	plist = NULL;
	ListPrint(plist);
}
int main()
{
	Test();
	return 0;
}

以下为运行测试类时的最终结果:(因最终链表销毁,故无法进行打印,assert自动截止)

三.顺序表&链表

CPU

L1cache

L2cache                 寄存器                                      分别遍历顺序表和链表

L3cache

(假设顺序表的地址为0x00123400)访问储存位置时,先看这个地址在不在缓冲区中,在就直接访问,不再就先加载到缓存,再访问。假设不命中,依次加载20byte到缓存(具体加载多大取决于硬件体系)

顺序表是连续的,故CPU高速缓存命中率高

链表是非连续的,每个节点都由一个指针,故CPU高速缓存命中率更低

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值