前言
在先前討論的鏈式存儲結構中,無論是單鏈表還是單循環鏈表,都只有一個指向後繼的指針,因此可以很容易找到它的後繼,但是要找它的前驅卻很麻煩。那麼可不可以再增加一個指向前驅的指針,這樣在遍歷鏈表時不僅能從前往後遍歷,也能從後往前遍歷呢?這就是我們今天所說的雙向鏈表。
顧名思義,雙向鏈表就是其結點中有兩個指針域,一個指向直接前驅,一個指向直接後繼。當然,鏈表的第一個結點無直接前驅,最後一個結點無直接後繼,設置爲空即可。
實現雙向鏈表的相關操作,思路上同單鏈表是一致的,唯一的區別是還需要考慮Prior指針的指向問題。
增加雙向鏈表的管理結構
我們同樣爲雙向鏈表設置管理結構,first指針指向頭結點,last指針指向最後一個結點,size保存鏈表結點的數量。同單鏈表一樣,在增加了管理結構之後,在做插入、刪除操作時,除了保證鏈表本身的正確性,還要考慮到管理結構的正確性,也就是注意first和last指針指向的正確,和size的數量正確。
雙向鏈表的插入
與單鏈表不同的是,雙向鏈表的改動除了涉及Next指針外,還涉及Prior指針,因此每當我們要執行插入操作時,一般要考慮四個指針的指向
- 待插入結點的Prior指針
- 待插入結點的Next指針
- 待插入結點前驅的Next指針
- 待插入結點後繼的Prior指針
只要想好了這四個指針的指向問題,雙向鏈表的操作也就沒什麼難點了
尾部插入
尾部插入只涉及兩個結點,因此只需要考慮兩個指針,以及管理結構的last指針即可
頭部與按值插入
頭部插入是直接插在頭結點後的位置,按值插入是找到插入位置後直接進行插入(如果是最後一位直接進行尾部插入),兩種方式都需要考慮四個指針的指向
同時,在設置四個指針指向的時候要注意先後的順序,避免出現指針丟失的問題
雙向鏈表的刪除
雙向鏈表的刪除比單鏈表的刪除方便,因爲可以找到直接前驅,所以也不需要使用修改值的方法了,直接修改直接前驅和直接後繼的指針就好了。尾部、頭部和按值刪除的方法都類似,只是涉及到的指針有區別,不詳談了。
排序與逆置
與單鏈表的思路一樣,在進行雙向鏈表的排序時,可以借鑑按值插入的思路,將原有的雙向鏈表斷開,再遍歷一個個取出結點,進行按值插入。而在進行逆置時,則進行頭部插入即可。
在這裏我是利用了已經寫好的方法,直接調用方法進行按值插入,再將原來的結點釋放掉。
while (q != NULL) {
//取出後續結點
s = q;
q = q->next;
//進行按值插入
insert_val(list, s->data);
//釋放原來的結點
free(s);
list->size --;
}
也可以不調用方法,通過設置指針的方式將結點重新連接到鏈表中
while (q != NULL) {
//取出後續結點
s = q;
q = q->next;
Node *p = list->first;
//尋找插入位置
while (p->next != NULL && p->next->data<s->data) {
p = p->next;
}
//判斷是否是最後一個
if (p->next == NULL) {
//是,進行尾部插入
s->next = NULL;
s->prior = list->last;
list->last->next = s;
list->last = s;
}else{
//s的後繼指向p的後繼
s->next = p->next;
//s的前驅指向p
s->prior = p;
//p後繼的前驅指向s
p->next->prior = s;
//p的後繼指向s
p->next = s;
}
逆置同理
全部代碼
DList.h
#ifndef DList_h
#define DList_h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define ElemType int
typedef struct Node{
ElemType data;
struct Node *prior;
struct Node *next;
}Node,*PNode;
typedef struct List{
PNode first;
PNode last;
int size;
}List;
//初始化雙鏈表
void InitDList(List *list);
//1.尾部插入
void push_back(List *list,ElemType x);
//2.頭部插入
void push_fount(List *list,ElemType x);
//3.展示
void show_list(List *list);
//4.尾部刪除
void pop_back(List *list);
//5.頭部刪除
void pop_fount(List *list);
//6.按值插入(要求插入前的鏈表是有序的(此處爲順序
void insert_val(List *list,ElemType x);
//7.按值查找
Node* find(List *list,ElemType x);
//8.獲取長度
int length(List *list);
//9.按值刪除
void delete_val(List *list,ElemType x);
//10.排序
void sort(List *list);
//11.逆置(前後轉換
void resver(List *list);
//12.清除單鏈表 ->只清除數據,保留頭結點
void clearList(List *list);
//13.摧毀 ->包括頭結點在內一起摧毀
void destroy(List *list);
//生成一個結點
Node* getNode(ElemType x);
#endif /* DList_h */
DList.cpp
#include "DList.h"
//初始化
void InitDList(List *list){
Node *s = (Node *)malloc(sizeof(Node));
assert(s != NULL);
list->first = list->last = s;
list->first->next = NULL;
list->first->prior = NULL;
list->size = 0;
}
//1.尾部插入
void push_back(List *list,ElemType x){
//獲得一個結點
Node *s = getNode(x);
//s的前驅指向最後一個結點
s->prior = list->last;
//最後一個結點的後繼指向s
list->last->next = s;
//重新設置last指針的指向
list->last = s;
list->size ++;
}
//2.頭部插入
void push_fount(List *list,ElemType x){
Node *s = getNode(x);
//判斷是否是第一個結點
if (list->first == list->last) {
//是第一個結點,s的後繼不需要設置
//s的前驅指向頭結點
//s->prior = list->first;
//重新設置first指針的指向,使s成爲新的首元結點
//list->first->next = s;
//重新設置last指針的指向
list->last = s;
}else{
//s的後繼指向首元結點
s->next = list->first->next;
//s的前驅指向頭結點
//s->prior = list->first;
//首元結點的前驅指向s
//s->next->prior = s;
list->first->next->prior = s;
//重新設置first指針的指向,使s成爲新的首元結點
list->first->next = s;
}
//if-else語句中有重複的部分,可以提出來
//s的前驅指向頭結點
s->prior = list->first;
//重新設置first指針的指向,使s成爲新的首元結點
list->first->next = s;
list->size ++;
}
//3.展示
void show_list(List *list){
Node *p = list->first->next;
while (p != NULL) {
printf("%4d",p->data);
p = p->next;
}
printf("\n");
}
//4.尾部刪除
void pop_back(List *list){
if (list->size == 0) {
printf("鏈表已空!\n");
return;
}
//方法1
Node *p = list->first;
//循環到鏈表的最後一位
while (p->next != NULL) {
p = p->next;
}
//將last指針指向倒數第二位(即p的前一位
list->last = p->prior;
//釋放該結點的空間
free(p);
//此時鏈表最後一位的後繼爲空
list->last->next = NULL;
list->size --;
/*
//方法2
Node *p = list->first;
//循環到鏈表倒數第二位
while (p->next != list->last) {
p = p->next;
}
//釋放最後一個結點的空間
free(p->next);
p->next = NULL;
//重新設置last指針的指向
list->last = p;
list->size --;
*/
}
//5.頭部刪除
void pop_fount(List *list){
if (list->size == 0) {
printf("鏈表已空!\n");
return;
}
Node *s = list->first->next;
//判斷是否是最後一個結點
if (s->next == NULL) {
//是,則s沒有後繼,不需要修改後繼的指針
//修改last指針的指向
list->last = list->first;
list->last->next = NULL;
}else{
//s的後繼的prior指針指向頭結點
s->next->prior = list->first;
//頭結點的next指針指向s的後繼
list->first->next = s->next;
}
//釋放該結點
free(s);
list->size --;
}
//6.按值插入(要求插入前的鏈表是有序的(此處爲順序
void insert_val(List *list,ElemType x){
Node *p = list->first;
//尋找插入位置
while (p->next != NULL && p->next->data<x) {
p = p->next;
}
//判斷是否是最後一個
if (p->next == NULL) {
//是,直接進行尾部插入
push_back(list, x);
}else{
Node *s = getNode(x);
//s的後繼指向p的後繼
s->next = p->next;
//s的前驅指向p
s->prior = p;
//p後繼的前驅指向s
p->next->prior = s;
//p的後繼指向s
p->next = s;
list->size ++;
}
}
//7.按值查找
Node* find(List *list,ElemType x){
Node *p = list->first->next;
while (p != NULL && p->data != x) {
p = p->next;
}
return p;
}
//8.獲取長度
int length(List *list){
return list->size;
}
//9.按值刪除
void delete_val(List *list,ElemType x){
if (list->size == 0) {
printf("鏈表已空!\n");
return;
}
Node *p = find(list, x);
if (p == NULL) {
printf("要刪除的值不存在!\n");
return;
}
//判斷是否是最後一個節點
if (p == list->last) {
//直接進行尾部刪除
pop_back(list);
}else{
//p後繼的前驅指向p的前驅
p->next->prior = p->prior;
//p前驅的後繼指向p的後繼
p->prior->next = p->next;
//釋放結點空間
free(p);
list->size --;
}
}
//10.排序
void sort(List *list){
if (list->size <= 1) {
//不需要排序
return;
}
Node *s = list->first->next;
Node *q = s->next;
//將鏈表斷開
list->last = s;
list->last->next = NULL;
while (q != NULL) {
//取出後續結點
s = q;
q = q->next;
//進行按值插入
insert_val(list, s->data);
//釋放原來的結點
free(s);
list->size --;
// Node *p = list->first;
// //尋找插入位置
// while (p->next != NULL && p->next->data<s->data) {
// p = p->next;
// }
//
// //判斷是否是最後一個
// if (p->next == NULL) {
// //是,直接進行尾部插入
// s->next = NULL;
// s->prior = list->last;
// list->last->next = s;
// list->last = s;
// }else{
//
// //s的後繼指向p的後繼
// s->next = p->next;
// //s的前驅指向p
// s->prior = p;
// //p後繼的前驅指向s
// p->next->prior = s;
// //p的後繼指向s
// p->next = s;
// }
}
}
//11.逆置(前後轉換
void resver(List *list){
if (list->size <= 1) {
//不需要逆置
return;
}
Node *s = list->first->next;
Node *q = s->next;
//將鏈表斷開
list->last = s;
list->last->next = NULL;
while (q != NULL) {
s = q;
q = q->next;
//直接進行頭部插入
push_fount(list, s->data);
//釋放原有結點
free(s);
list->size --;
}
}
//12.清除單鏈表 ->只清除數據,保留頭結點
void clearList(List *list){
if (list->size == 0) {
printf("鏈表已空!\n");
return;
}
Node *s = list->first;
Node *p = s->next;
//遍歷整個鏈表
while (p != NULL) {
s = p;
p = p->next;
//釋放結點
free(s);
list->size --;
}
list->last = list->first;
list->last->next = NULL;
}
//13.摧毀 ->包括頭結點在內一起摧毀
void destroy(List *list){
clearList(list);
free(list->first);
list->first = list->last = NULL;
}
//生成一個結點
Node* getNode(ElemType x){
Node *s = (Node *)malloc(sizeof(Node));
assert(s != NULL);
s->data = x;
s->prior = NULL;
s->next = NULL;
return s;
}
Main.cpp
#include "DList.h"
int main(int argc, const char * argv[]) {
List myList;
InitDList(&myList);
//保存要輸入的數據
ElemType item;
//保存要查找的地址
Node *p = NULL;
int select = 1;
while (select) {
printf("**************************************\n");
printf("* [1] push_back [2] push_front *\n");
printf("* [3] show_list [4] pop_back *\n");
printf("* [5] pop_front [6] insert_val *\n");
printf("* [7] find [8] length *\n");
printf("* [9] delete_val [10] sort *\n");
printf("* [11] resver [12] clear *\n");
printf("* [13*] destroy [0] quit_system*\n");
printf("**************************************\n");
printf("請選擇:");
scanf("%d",&select);
if (select == 0) {
break;
}
switch (select) {
case 1:
//尾部插入
printf("請輸入要插入的數據(-1結束):");
scanf("%d",&item);
while (item != -1) {
push_back(&myList, item);
scanf("%d",&item);
}
break;
case 2:
//頭部插入
printf("請輸入要插入的數據(-1結束):");
scanf("%d",&item);
while (item != -1) {
push_fount(&myList, item);
scanf("%d",&item);
}
break;
case 3:
//展示單鏈表
show_list(&myList);
break;
case 4:
//尾部刪除
pop_back(&myList);
break;
case 5:
//頭部刪除
pop_fount(&myList);
break;
case 6:
//按值插入
printf("請輸入要插入的數據:\n");
scanf("%d",&item);
insert_val(&myList, item);
break;
case 7:
//按值查找
printf("請輸入要查找的值:\n");
scanf("%d",&item);
p = find(&myList, item);
if (p == NULL) {
printf("要查找的數據不存在!\n");
}else{
printf("地址爲:%p\n",p);
}
break;
case 8:
//展示鏈表長度
printf("鏈表的長度爲:%d\n",length(&myList));
break;
case 9:
//按值刪除
printf("請輸入要刪除的值:\n");
scanf("%d",&item);
delete_val(&myList, item);
break;
case 10:
//排序
sort(&myList);
break;
case 11:
//逆置(前後轉換
resver(&myList);
break;
case 12:
//清除
clearList(&myList);
break;
case 13:
destroy(&myList);
break;
default:
printf("輸入的命令有誤,請重新插入\n");
break;
}
}
return 0;
}