链表

主要讨论:链表是啥?都有什么形式?与数组相比的优势?以及在STL标准库中list<T>所构造的是否为普通意义上的链表?

1.链表的定义:(来自wiki: https://en.wikipedia.org/wiki/Linked_list)

In computer science, a linked list is a linear collection of data elements, called nodes, each pointing to the next node by means of a pointer. It is a data structure consisting of a group of nodes which together represent a sequence. Under the simplest form, each node is composed of data and a reference (in other words, a link) to the next node in the sequence. This structure allows for efficient insertion or removal of elements from any position in the sequence during iteration.

(来自百度百科)

链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)O(1)

2.链表的形式:(以下内容来自《数据结构与算法 c++版》Adam Drozdek 3章)

2.1 单链表

如果在序列中的节点只含有指向它的后继节点的链接,该链表称为单向链表。

以下是单向链表的一些操作构造及其注释(书中代码)

intSLLST.h
#ifndef INT_LINKED_LIST
#define INT_LINKED_LIST class IntSLLNode{//构造一个节点类,存储info 和下一个几点的指针 public : int info; IntSLLNode* next; IntSLLNode(int el, IntSLLNode *ptr = 0){ info = el; next = ptr; }//使用构造函数对节点进行初始化 }; class IntSLList{//用来对链表进行外部操作 public: IntSLList(){ head = tail = 0; } ~IntSLList(); int isEmpty(){ return head == 0; } void addToHead(int);//在表头加入数据点 void addToTail(int);//在表尾部加入数据 int deleteFromHead();//删除头部节点, int deleteFromTail(); void deleteNode(int);//删掉具有某一指定值的节点 bool isInList(int el); private://这里要求对指针的操作是对外不可见的,也就是要形成一种假象:我们只管加入数据即可 IntSLLNode *head, *tail;//两个指向IntSLLNode的指针 }; #endif intSLLST.cpp #include <iostream> #include "intSLLST.h" IntSLList::~IntSLList(){ //析构函数 for (IntSLLNode *p; !isEmpty();) { p = head->next;//遍历每一个节点 delete head;//并删除它 head = p;//直到该节点为空为止 } } void IntSLList::addToHead(int el) { head = new IntSLLNode(el, head); if (tail == 0) tail = head;//表尾部如果有值不变,如果没有值,就要把刚加的数据作为表尾部,则此时两个指针指向同一个node } void IntSLList::addToTail(int el) { if (tail != 0) { tail->next = new IntSLLNode(el);//当前尾部节点的next指向 tail = tail->next;//设定新的尾部指针 } else { head = tail = new IntSLLNode(el);//原先为空链表了,现在只有一个节点,这里只给定节点数据,节点的next指针为0 } } int IntSLList::deleteFromHead() { int el = head->info; IntSLLNode *tmp = head; if (head == tail) head = tail = 0; else head = head->next; delete tmp; return el; } int IntSLList::deleteFromTail() { int el = tail->info; if (head == tail) { delete head; head = tail = 0; } else { IntSLLNode *tmp; for (tmp = head; tmp->next != tail; tmp = tmp->next);//将指针移动到tail的前一个 delete tail; tail = tmp; tail->next = 0; } return el; } void IntSLList::deleteNode(int el) {//查找任意节点,并把它删除掉,然后把前驱和后继链接起来,后继好找next即是,但前驱需要另外保存 if (head != 0)//保证不出现从空链表中删除节点的情况 { if (head == tail && el == head->info) {//只有一个节点,且就是要删的那个 delete head; head = tail = 0; } else {//如果不止一个节点,那就从头开始判断 if (el == head->info) { IntSLLNode *tmp = head; head = head->next; delete tmp; } else{ IntSLLNode *pred, *temp;//pred是指向前驱节点的指针,temp指向当前节点,temp->next 指向后继节点 for (pred = head, temp = head->next; temp != 0 && !(temp->info == el); pred = pred->next, temp = temp->next); //这个for是一个移动查找的过程,终止条件是:要么找到了结尾,要么找到了要找的值temp->info == el if (temp != 0) { pred->next = temp->next;//令 前驱=后继 if (temp == tail) { tail = pred;//如果要删除的节点在尾部,那么他的前驱就是新的尾部节点了 } delete temp; } } } } } bool IntSLList::isInList(int el) { IntSLLNode *temp; for (temp = head; temp != 0 && !(temp->info == el); temp = temp->next); return temp != 0; }

2.2双向链表

在单向链表中存在固有的问题,就是无法直接访问前驱节点,需要通过遍历完成,那么自然就衍生出了双向链表。即,在单向链表的定义基础上多加一个指向前驱的指针成员,但这会使得链表的维护略微复杂一点。

2.3循环链表

当每一个节点都有后继的时候,让原先单向链表中的尾部节点tail->next=head时,整个链表就形成一个环形数据链,链表长度固定。可以用于多个进程共享资源时,进行循环分配(没有例子,表示不解)。这个循环链表也同样可以由双向链表构造。

2.4跳跃链表

感觉这种链表完全失去了链表本身应该具有的性质,他的查找是变得高效了(也仅是对链表而言),但是顺序访问和插入删除变得繁琐而低效,给人一种邯郸学步的感觉。

2.more

还有自组织链表,和稀疏链表这里没做详细阅读。

3.链表在STL库中的实现

3.1 list

#include <list>

实质是一种双链表,在其外部包装了一些常用操作的函数,提供诸如查找,首尾插入,删除,按某特殊要求排序等功能,但因为它的本质是链表,所以list 不提供向量容器(如vector)所具有的某些函数,如 at(),capacity(),reserve()。这也就意味着list 虽然在首尾进行操作是比vector更方便,但不能对某固定位置的元素提供随机的访问和操作。进而引进了另外一种链表deque.

3.2 deque

#include<deque>

双端队列,也是一种双链表,但是STL中为其加入了新功能,即可以随机访问队列中的任何位置。也就是说这种双端队列整合了向量和链表的功能优势。在vector中没有pop_front(),push_front(),但在deque中提供这种操作,这篇博客中提供了他们的性能比较:

http://www.cppblog.com/sailing/articles/161659.html

原文地址:https://www.cnblogs.com/simayuhe/p/6177999.html