双向链表
双向链表,即双向带头循环链表,结构最复杂,一般用在单独存储数据。它有两个指针,一个指向前驱结点的前驱指针,一个指向后置结点的后置指针,并且还保存着相应的数据。
逻辑结构如图所示:
双向链表的声明
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
双向链表的接口实现
1. ListNode* BuyListNode(LTDataType x)
描述:申请一个双向链表的结点
实现:
ListNode* BuyListNode(LTDataType x){
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
2. ListNode* ListInit()
描述:对双向链表进行初始化
实现:
ListNode* ListInit(){
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
3. void ListPrint(ListNode* plist)
描述:对双向链表进行打印
实现:
void ListPrint(ListNode* plist){
assert(plist);
ListNode* cur = plist->next;
while(cur != plist)
{
printf("%d ",cur->data);
cur = cur->next;
}
printf("\n");
}
4. void ListInsert(ListNode* pos, LTDataType x)
描述:在pos位置之前进行插入
实现:
void ListInsert(ListNode* pos, LTDataType x){
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* posPrev = pos->prev;
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
5.void ListErase(ListNode* pos)
描述:删除pos位置的结点
实现:
void ListErase(ListNode* pos){
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
free(pos);
posPrev->next = posNext;
posNext->prev = posPrev;
}
6. void ListPushFront(ListNode* plist, LTDataType x)
描述:对双向链表进行头插
实现:
方法1:
void ListPushFront(ListNode* plist, LTDataType x){
assert(plist);
ListNode* newnode = BuyListNode(x);
ListNode* next = plist->next;
plist->next = newnode;
newnode->prev = plist;
newnode->next = next;
next->prev = newnode;
}
方法2:
ListInsert(plist->next,x);
7.void ListPushBack(ListNode* plist, LTDataType x)
描述:双向链表尾插
实现:
方法1:
void ListPushBack(ListNode* plist, LTDataType x){
assert(plist);
ListNode* tail = plist->prev;
ListNode* newnode = BuyListNode(x);
//尾的下一个指向新结点,新结点的前一个指向尾,新结点的下一个指向头,头结点的前>一个指向新结点
tail->next = newnode;
newnode->prev = tail;
newnode->next = plist;
plist->prev = newnode;
}
方法2:
ListInsert(plist,x);
8.void ListPopFront(ListNode* plist)
描述:双向链表头删
实现:
方法1:
void ListPopFront(ListNode* plist){
assert(plist);
//若无数据,则不删除
assert(plist->prev!=plist);
ListNode* popNode = plist->next;
ListNode* next = popNode->next;
free(popNode);
plist->next = next;
next->prev = plist;
}
方法2:
ListErase(plist->next);
9. void ListPopBack(ListNode* plist)
描述:双向链表尾删
实现:
方法1:
void ListPopBack(ListNode* plist){
assert(plist);
ListNode* tail = plist->prev;
ListNode* tailPrev = tail->prev;
//如果最后一个数据删完以后,链表恢复到最开始状态,只有头结点,并且是循环状态
free(tail);
tailPrev->next = plist;
plist->prev = tailPrev;
}
方法2:
ListErase(plist->prev);
顺序表和链表概念选择题
- 在一个长度为n的顺序表中删除第i个元素,要移动_______个元素。如果要在第i个元素前插入一个元素,要后
移_________个元素。
A n-i,n-i+1
B n-i+1,n-i
C n-i,n-i
D n-i+1,n-i+1
A,删除第 i 个元素,即将 i 之后的元素移动到 i 的位置,总共需要移动 n - i 次,而如果要在第 i 个元素前插入一个元素,则需要将包含 i 在内的元素,向后移动 1 个单位,则共需移动 n - i + 1
- 取顺序表的第 i 个元素的时间同i的大小有关()
A 对
B 错
B,顺序表是支持下标访问的,与 i 的大小无关
- 在一个具有 n 个结点的有序单链表中插入一个新结点并仍然保持有序的时间复杂度是( ) 。
A O(1)
B O(n)
C O(n2)
D O(nlog2n)
B,单链表是有序的,则插入的新结点,需要和从链表的头结点开始一一比较大小,直到找到小于(大于)的结点,其中最坏的情况就是一直比较到链表末尾,都没有结果,这时说明该新结点为最大值(最小值),遍历链表的次数为n次,时间复杂度为o(n)。
- 下列关于线性链表的叙述中,正确的是( )。
A 各数据结点的存储空间可以不连续,但它们的存储顺序与逻辑顺序必须一致
B 各数据结点的存储顺序与逻辑顺序可以不一致,但它们的存储空间必须连续
C 进行插入与删除时,不需要移动表中的元素
D 以上说法均不正确
C,链表在进行删除或插入时,是不需要移动表中的元素,只需要变化链表上对应的指针就行。
- 设一个链表最常用的操作是在末尾插入结点和删除尾结点,则选用()最节省时间。
A 单链表
B 单循环链表
C 带尾指针的单循环链表
D 带头结点的双循环链表
D,双向链表的头结点可以通过前驱指针直接访问尾结点,而单链表则只能通过next指针,遍历到尾指针。
- 链表不具有的特点是()。
A 插入、删除不需要移动元素
B 不必事先估计存储空间
C 可随机访问任一元素
D 所需空间与线性表长度成正比
C,链表不能随机访问元素
- 在一个单链表中,若删除 P 所指结点的后续结点,则执行( )?
A p = p->next;p->next = p->next->next;
B p->next = p->next;
C p->next = p->next->next;
D p = p->next->next
C,要删除 p 的后一个结点,只需将 p 的 next 指针指向后一个结点的下一个结点,即指向p->next->next即可。
- 一个单向链表队列中有一个指针p,现要将指针r插入到p之后,该进行的操作是____。
A p->next=p->next->next
B r->next=p;p->next=r->next
C r->next=p->next;p->next=r
D r=p->next;p->next=r->next
E r->next=p;p->next=r
F p=p->next->next
C,首先要让 r 的next指针保存 p 的next指针,然后再将 p 的next 指向 r ,这样插入后,顺序就不会变。