赞
踩
结点定义:
typedef int ListDataType;
typedef struct LinkedListNode
{
struct LinkedListNode* next;
struct LinkedListNode* prev;
ListDataType data;
}LinkedListNode;
接口定义:
LinkedListNode* LinkedListInit(); void LinkedListPrint(LinkedListNode* phead); bool LinkedListEmpty(LinkedListNode* phead); void LinkedListPushBack(LinkedListNode* phead, ListDataType x); void LinkedListPushFront(LinkedListNode* phead, ListDataType x); void LinkedListPopBack(LinkedListNode* phead); void LinkedListPopFront(LinkedListNode* phead); LinkedListNode* LinkedListFind(LinkedListNode* phead, ListDataType x); // 在pos之前插入 void LinkedListInsert(LinkedListNode* pos, ListDataType x); // 删除pos位置的值 void LinkedListErase(LinkedListNode* pos); void LinkedListDestroy(LinkedListNode* phead);
我们将申请结点的代码封装成函数,方便后续使用
LinkedListNode* CreateLinkedListNode(ListDataType x)
{
LinkedListNode* newnode = (LinkedListNode*)malloc(sizeof(LinkedListNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
由于是带头结点的双向链表,因此在使用链表前,我们需要对链表进行初始化。
LinkedListNode* LinkedListInit()
{
LinkedListNode* phead = CreateLinkedListNode(230510);
phead->next = phead;
phead->prev = phead;
return phead;
}
遍历链表,值得说的是,带头结点的双向链表的循环结束条件是 cur != phead
void LinkedListPrint(LinkedListNode* phead)
{
assert(phead);
LinkedListNode* cur = phead->next;
printf("guard<->");
while (cur != phead)
{
printf("%d<->", cur->data);
cur = cur->next;
}
printf("\n");
}
尾插时,需要先找到尾结点,然后将新结点插入到尾结点后面。
void LinkedListPushBack(LinkedListNode* phead, ListDataType x)
{
assert(phead);
// 1.找到尾结点
LinkedListNode* tail = phead->prev;
// 2.插入到尾结点后面
LinkedListNode* newnode = CreateLinkedListNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
第一种写法,要注意现将newnode后面的结点进行链接,然后再讲newnode链接到phead后面。
void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
assert(phead);
LinkedListNode* newnode = CreateLinkedListNode(x);
// 与原来的第一个数据结点链接
newnode->next = phead->next;
phead->next->prev = newnode; // newnode->next->prev = newnode;
// newnode变为新的第一个数据结点
phead->next = newnode;
newnode->prev = phead;
}
第二种写法(推荐写法),我们使用变量记录phead的next,记为first,这样newnode插入到phead和first之间,这样逻辑比较清楚。
void LinkedListPushFront(LinkedListNode* phead, ListDataType x) { assert(phead); LinkedListNode* newnode = CreateLinkedListNode(x); // 用变量记录第一个结点 LinkedListNode* first = phead->next; // 与原来的第一个数据结点链接 newnode->next = first; first->prev = newnode; // newnode变为新的第一个数据结点 phead->next = newnode; newnode->prev = phead; }
phead的prev是尾tail,tail的prev是tailPrev。
void LinkedListPopBack(LinkedListNode* phead)
{
assert(phead);
LinkedListNode* tail = phead->prev;
LinkedListNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
上面代码的问题是链表为空的情况报错,于是我们在该函数内部对空链表进行断言:
void LinkedListPopBack(LinkedListNode* phead)
{
assert(phead);
assert(!LinkedListEmpty(phead)); // 删除时链表不能为空
LinkedListNode* tail = phead->prev;
LinkedListNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
判断链表为空逻辑:
bool LinkedListEmpty(LinkedListNode* phead)
{
assert(phead);
return phead->next == phead;
}
使用链表为空的断言:
assert(!LinkedListEmpty(phead)); // 链表为空时error
头删时需要将第一个结点删除,很容易便想到以下代码:
void LinkedListPopFront(LinkedListNode* phead)
{
assert(phead);
assert(!LinkedListEmpty(phead)); // 删除时链表不能为空
LinkedListNode* next = phead->next;
phead->next = next->next;
phead->next->prev = phead; // next->next->prev = phead;
free(next);
}
为了提高可读性,推荐使用以下代码,定义first和second两个变量指向第一个和第二个:
void LinkedListPopFront(LinkedListNode* phead)
{
assert(phead);
assert(!LinkedListEmpty(phead)); // 删除时链表不能为空
LinkedListNode* first = phead->next;
LinkedListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
查找的本质就是遍历链表
LinkedListNode* LinkedListFind(LinkedListNode* phead, ListDataType x)
{
assert(phead);
LinkedListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
pos的来源一般是find的结果
void LinkedListInsert(LinkedListNode* pos, ListDataType x)
{
assert(pos);
LinkedListNode* prev = pos->prev;
LinkedListNode* newnode = CreateLinkedListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
有了上面的insert在任意位置插入,我们可以修改尾插代码:
void LinkedListPushBack(LinkedListNode* phead, ListDataType x)
{
assert(phead);
// 在phead之前插入,也就是尾插
LinkedListInsert(phead, x);
}
同理也可以修改头插代码:
void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
assert(phead);
LinkedListInsert(phead->next, x);
}
同insert一样,pos也应该是调用者通过find返回的结果。
void LinkedListErase(LinkedListNode* pos)
{
assert(pos);
LinkedListNode* posPrev = pos->prev;
LinkedListNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
有了上面的erase在任意位置删除,我们可以修改尾删的代码:
void LinkedListPopBack(LinkedListNode* phead)
{
assert(phead);
assert(!LinkedListEmpty(phead));
LinkedListErase(phead->prev);
}
同理也可以修改头删的代码:
void LinkedListPopFront(LinkedListNode* phead)
{
assert(phead);
assert(!LinkedListEmpty(phead));
LinkedListErase(phead->next);
}
记得释放头结点phead:
void LinkedListDestroy(LinkedListNode* phead)
{
assert(phead);
LinkedListNode* cur = phead->next;
while (cur != phead)
{
LinkedListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
带头结点、双向、循环链表的实现都非常的简单,需要注意判空条件与遍历终止的条件。
在代码写法上,对于某个节点的前一个或后一个的问题,我们最好分别使用变量去记录,这样代码的逻辑更清晰,可读性更高。例如尾插时的tail,尾删时的tail和tailPrev,以及头删时的first与second,erase中的posPrev与posNext,这些变量的使用,提高了代码的可读性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。