主要讨论:链表是啥?都有什么形式?与数组相比的优势?以及在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