链表与顺序表的比较及双链表的实现

一、顺序表和链表的区别和联系:

        这两个结构各有优劣势,很难说谁好谁坏,严格来说他们两个相辅相成。

Ⅰ、顺序表:

优点:

        ①因为其物理结构连续,支持随机访问(用下标访问);

        ②CPU高速缓存命中率更高(相比链表);

        高速缓存是为了解决CPU速率和内存访问速率差距过大问题,所以读取数据之前会将内存中的数据提前加载到缓冲区中。当分别遍历链表或顺序表时,CPU在读取数据时,先看这个数据在不在缓存区中,在就直接访问,不在就先加载到缓存中(首先在高速缓存中查找所需数据判断是否命中,如果命中,立即返回缓存中的副本给CPU;如果不命中,从存储器层次结构中较低层次中取),假设不命中,则一次加载20byte到缓存(具体多大看硬件体系),因为顺序表物理结构是连续的,所以加载的时候可以加载一段连续的数据,增加其命中率。

缺点:

        ①头部/中部插入/删除数据效率低(时间复杂度O(N));

        ②空间不够需要增容,增容代价稍高(开辟新空间,拷贝数据,释放旧空间);并且为避免频繁增容,一般我们都是按一定的倍数增长,用不完则存在一定的浪费。

Ⅱ、链表(双向带头循环链表):

优点:

        ①任意位置插入删除效率高(时间复杂度O(1));

        ②按需申请释放空间,不存在空间的浪费。

缺点:

        ①不支持随机访问,意味着一些排序、二分查找等在这种结构上不适用。

        ②有可能会造成大量内存碎片化。

        ③CPU高速缓存命中率低。

二、双向带头循环链表实现

结构:

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;

 创建头结点:(头节点不存储数据)

// 创建返回链表的头结点.
ListNode* ListInit()
{
	ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
	}
	ListNode* phead = tmp;
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

打印:

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

创建新节点、尾插尾删:

//创建新节点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
	}
	ListNode* newnode = tmp;
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* tail = pHead->prev;
	ListNode* newnode = BuyListNode(x);

	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = pHead;
	pHead->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->prev != pHead);//避免链表只有头结点
	ListNode* tail = pHead->prev->prev;

	free(tail->next);
	tail->next = pHead;
	pHead->prev = tail;
}

头插头删:

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyListNode(x);
	newnode->next = pHead->next;
	newnode->prev = pHead;
	pHead->next = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead && pHead->next != pHead);

	ListNode* next = pHead->next->next;
	free(pHead->next);
	pHead->next = next;
	next->prev = pHead;
}

查找:

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead && pHead->next != pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

任意位置插入删除:

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyListNode(x);
	ListNode* prev = pos->prev;
	newnode->prev = prev;
	prev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* next = pos->next;
	ListNode* prev = pos->prev;
	next->prev = prev;
	prev->next = next;
	free(pos);
	pos = NULL;
}

可以用任意位置的insert/erase复用尾插头插/尾删头删:

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead, x);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->prev != pHead);//避免链表只有头结点

	ListErase(pHead->prev);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead && pHead->next != pHead);

	ListErase(pHead->next);
}

所以如何快速实现一个链表的增删查改,可以先写insert/erase,其他大部分可以复用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值