【双链表】
双链表是在单链表的基础上在每个结点中多加入了前驱指针 prior
,其指向当前结点的前驱结点
1 2 3 4 5 6
| template <typename T> struct DNode { T data; DNode<T> *prior; DNode<T> *next; };
|
在单链表中,在进行插入与删除时,若仅知道待操作结点,是无法取得其前驱结点的,而由于双链表引入了前驱指针,这就使得双链表在插入与删除时,当仅知道待操作结点时,能够快速得知其前驱结点
此外,对于最基本的 CRUD 操作,除非已经给出待操作结点(此时双链表插入与删除为 $O(1)$ ),否则单链表与双链表都需要从头到尾按序遍历,时间复杂度都是 $O(n)$
双链表的结构优势在于可以在 $O(1)$ 的时间内找到前驱结点,若算法需要对待操作结点的前驱结点大量处理,则双链表比单链表更有优势,但若从工程角度考量,双链表的维护性和可读性都更低
为提高维护性与可读性,双链表一般采取带头结点的实现,其实现类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| template <typename T> class DoubleLinkList { public: DoubleLinkList(); DoubleLinkList(T data[], int len); ~DoubleLinkList(); int getLen(); bool empty(); void print(); DNode<T> *getNodeByValue(T elem); DNode<T> *getNodeByLocate(int pos); bool insertNextNode(DNode<T> *p, T elem); bool insertPreNode(DNode<T> *p, T elem); bool insertNode(int pos, T elem); bool deleteNodeByLocate(DNode<T> *p, T &elem); void headInsert(T data[], int len); void tailInsert(T data[], int len); private: DNode<T> *first; };
|
需要说明的是,对于单链表和双链表来说,除了两者的插入与删除操作不同外,其余操作均相同
【初始化表与销毁表】
初始化与双链表的建立
对于双链表来说,其与单链表相似,在初始化完毕后,即可对于给定的数据序列 data[]
来建立双链表,建立双链表的两种方式与单链表的类似,同样一种是头插法,另一种是尾插法
在进行初始化时,需要为头指针 first
建立一个头结点,并使头结点的前驱指针 prior
永远指向 NULL
,代表无前驱;令头结点的后继指针 next
暂时指向 NULL
,代表目前是空表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| template <typename T> DoubleLinkList<T>::DoubleLinkList() { first = (DNode<T> *)malloc(sizeof(DNode<T>)); first->prior = NULL; first->next = NULL; } template <typename T> DoubleLinkList<T>::DoubleLinkList(T data[], int len) { first = (DNode<T> *)malloc(sizeof(DNode<T>)); first->prior = NULL; first->next = NULL;
this->headInsert(data, len); }
|
双链表的销毁
1 2 3 4 5 6 7 8
| template <typename T> DoubleLinkList<T>::~DoubleLinkList() { while (first != NULL) { DNode<T> *p = first; first = first->next; free(p); } }
|
【基本操作】
求表长
1 2 3 4 5 6 7 8 9 10
| template <typename T> int DoubleLinkList<T>::getLen() { int len = 0; DNode<T> *p = first; while (p->next != NULL) { p = p->next; len++; } return len; }
|
判空
1 2 3 4 5 6
| template <typename T> bool DoubleLinkList<T>::empty() { if (first->next == NULL) return true; return false; }
|
输出
1 2 3 4 5 6 7 8 9
| template <typename T> void DoubleLinkList<T>::print() { DNode<T> *p = first->next; while (p != NULL) { cout << p->data << " "; p = p->next; } cout << endl; }
|
【查找】
按值查找
1 2 3 4 5 6 7
| template <typename T> DNode<T> *DoubleLinkList<T>::getNodeByValue(T elem) { DNode<T> *p = first->next; while (p != NULL && p->data != elem) p = p->next; return p; }
|
按位查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| template <typename T> DNode<T> *DoubleLinkList<T>::getNodeByLocate(int pos) { if (pos < 0) return NULL; if (pos == 0) return first;
DNode<T> *p; p = first; int now_pos = 0;
while (p != NULL && now_pos < pos) { p = p->next; now_pos++; } return p; }
|
【插入】
后插
后插,是指在给定 p
结点后插入元素 elem
,一般用于按位插入,不单独使用
由于对指针的操作顺序有很多,但由于双向链表实质上可看作两条反向的单链表,所以插入操作的核心是:先处理每个方向的远端指针,再处理近端指针
原则上,插入一个结点需要连接 4 个指针,但考虑插入时的特殊情况,即在空表或表尾插入一个结点时,新结点的右指针指向为空,此时只需连接 3 个指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| template <typename T> bool DoubleLinkList<T>::insertNextNode(DNode<T> *p, T elem) { if (p == NULL) return false;
DNode<T> *s = (DNode<T> *)malloc(sizeof(DNode<T>));
s->data = elem; s->next = p->next; s->prior = p;
p->next = s;
if (s->next != NULL) s->next->prior = s;
return true; }
|
前插
前插与后插一样,一般用于按位插入,不单独使用
由于对指针的操作顺序有很多,但由于双向链表实质上可看作两条反向的单链表,所以插入操作的核心是:先处理每个方向的远端指针,再处理近端指针
原则上,插入一个结点需要连接 4 个指针,但考虑插入时的特殊情况,即在空表或表尾插入一个结点时,新结点的右指针指向为空,此时只需连接 3 个指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| template <typename T> bool DoubleLinkList<T>::insertPreNode(DNode<T> *p, T elem) { if (p == NULL) return false;
DNode<T> *s = (DNode<T> *)malloc(sizeof(DNode<T>));
s->data = elem; s->prior = p->prior; s->next = p;
s->prior->next = s;
p->prior = s;
return true; }
|
按位插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| template <typename T> bool DoubleLinkList<T>::insertNode(int pos, T elem) { if (pos < 1) return false;
DNode<T> *p = getNodeByLocate(pos - 1); if (p == NULL) return false; return insertNextNode(p, elem);
}
|
【删除】
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template <typename T> bool DoubleLinkList<T>::deleteNodeByLocate(DNode<T> *p, T &elem) { if (p == NULL) return false; elem = p->data; if (p->next == NULL) p->prior->next = NULL; else { p->next->prior = p->prior; p->prior->next = p->next; } free(p); return true; }
|
【头插法与尾插法】
头插法
对于双链表的头插法,每次插入都是对头结点进行后插操作
1 2 3 4 5
| template <typename T> void DoubleLinkList<T>::headInsert(T data[], int len) { for (int i = 0; i < len; i++) insertNextNode(first, data[i]); }
|
尾插法
对于双链表的尾插法,不断地申请新结点,填充数据域,并通过尾指针 tail
来建立后继关系与前驱关系
1 2 3 4 5 6 7 8 9 10 11 12
| template <typename T> void DoubleLinkList<T>::tailInsert(T data[], int len) { DNode<T> *tail = first; for (int i = 0; i < len; i++) { DNode<T> *p = (DNode<T> *)malloc(sizeof(DNode<T>)); p->data = data[i]; tail->next = p; p->prior = tail; tail = p; } tail->next = NULL; }
|