BDS之链表

来源:互联网 发布:淘宝童装小模特多少钱 编辑:程序博客网 时间:2024/05/16 08:46

 链表结构在日常的使用很广泛, 大大小小的软件系统或多或少,或直接或间接的都使用了链表,而关于链表,其又可以分为多种类型,常见的有静态链表,单向链表,双向链表,循环链表等等,除此之外,还有带头结点和不带头结点的链表之分,不过总体来说,这些链表在形态结构上都是类似的。

静态链表

静态链表是用数组实现的链式存储结构,典型的如下:

#define LIST_SIZE   10typdef unsinged int uInt;typedef strcut list_node{   eType e;   uInt     next;}st_link_list[LIST_SIZE];

相比于线性顺序存储结构,静态链表的最大优点在于删除和插入元素不需要移动任何元素,只需要修改“指针”,这里并不是真正意思上的指针,而实质上是静态数组的索引。

       

 静态链表存储结构图1              

 

存储有{a,b}两个元素的静态链表结构图  2     

  

  插入元素c后的静态链表结构图3

这里需要说明的是,静态链表将所有的空闲节点组织为备用链表,初始时数组下标为0的单元为该链表的头结点,而st_link_list[LIST_SIZE-1],也就是最后一个元素为链表的头结点,也就是说,静态链表上有两个链表,一个链表上链接的是线性表的结点,另一个链表上链接的是所有未被使用的结点。在图2和图3中我们说明链表加入元素时的过程,开始图2只有a,b两个元素,随后加入元素c后如图3.

单链表

单链表是最基本的具有动态特性的链式存储结构,它的存储结构如下:

typedef struct _list_node{   eType data;   struct _list_node* next;}list_node,*plist_node;

链表中当前的元素的next元素通过指针可以访问下个元素,对于删除和插入的操作,只是对链表的next指针进行操作,同时,由于是动态的增长,单链表比动态链表更加灵活。


                                                           带有头结点的单向链表示例图

单链表的结构非常相比静态链表简单很多,如上图链表有三个元素4,7,9.

双向链表

双向链表是对单链表进行的拓展,它的存储结构如下:

typedef struct _du_node{   eType data;   struct _du_node* next;   struct _du_node* prior;}du_node,*pdu_node;

双向链表的节点元素包含了指向前驱节点和后继节点的指针,因此可以通过前驱指针和后继指针访问整个链表,但相比单链表,对于删除和插入操作,相对来说稍微复杂一点。

一般我们讨论的双向链表都是指循环双向链表,它的结构图如下:

                                                         

                                                                                双向链表存储结构图


                                                                   带头结点的循环双链表示例图

循环链表

循环链表是使链表满足环状的特性,它的存储结构和上面介绍的链表结构保持一致,只是将链表中的尾结点(tail)和首结点(head)关联起来,对于双向链表来说,它同样可以通过任何一个结点访问整个链表,并同时具有顺序和逆序访问链表的能力。

一份双向循环链表的简单实现:

#define _NODEBUG#include "lib_error.h"#include <assert.h>#ifndef DU_LINK_H__#define DU_LINK_H__typedef int Status;typedef struct _du_node{eType data;struct _du_node* next;struct _du_node* prior;}du_node,*pdu_node;/*initial link ,create a du_node struct.*/Status initial(pdu_node* head){*head = (pdu_node)malloc(sizeof(du_node));if(!(*head)){#ifndef _NODEBUGprintf("FILE:%s LINE:%d,error msg(code=%d),out of mem in initial method.\n",__FILE__,__LINE__,OUT_OF_MEM);#endifreturn OUT_OF_MEM;}(*head)->next=(*head)->prior=*head;return STATUS_SUCCESS;}/*free the heap node of the dual link*/Status clear(du_node* head){du_node* p=head->next;du_node* q;while(p!=head){q=p;p=p->next;free(q);}free(head);return STATUS_SUCCESS;}/** if dual link is empty return true ,else return false*/bool is_empty(du_node* head){return head->next == head ? true:false;}/**search method: search the special element from the dual link *return the pointer of this element or null*/pdu_node search(du_node* head,eType e,bool(*eq)(eType e1,eType e2)){du_node* p;if(!head) return null;p=head->next;while(p!=head){if(eq(p->data,e)){return p;}p=p->next;}return null;}/**get_elem method: get the element data at the position of index i.*return : pe stores the return value*/Status get_elem(du_node* head,int i,eType* pe){du_node* p;int j=1;if(!head)return STATUS_ERROR;p=head->next; //first nodewhile(p!=head&&j<i){p=p->next;++j;}if(p==head || j>i)return STATUS_NOT_FOUND;*pe=p->data;return STATUS_OK;}/**locate_elem: find the first element's index which meet the conditions with comparing function. *return :the element's index.*/int locate_elem(du_node* head,eType e,Status(*compare)(eType,eType)){du_node* p;int i=1;if(!head)return STATUS_ERROR;p=head->next;while(p!=head){if(compare(p->data,e))return i;++i;p=p->next;}return STATUS_NOT_FOUND;}/* *insert method: insert a eType data into link at the index position. *return STATUS_SUCCESS if success*/Status insert(du_node* head,int i,eType e){du_node *new_node,*p;if(i<0) return INVALID_POSITION;new_node=(pdu_node)malloc(sizeof(du_node));if(!new_node){#ifndef _NODEBUGprintf("FILE:%s LINE:%d,error msg(code=%d),error msg(code=%d):out of mem in initial method.\n",\__FILE__,__LINE__,OUT_OF_MEM);#endifreturn OUT_OF_MEM;}new_node->data=e;if(is_empty(head)){//insert head.head->next=new_node;head->prior=new_node;new_node->next=head;new_node->prior=head;return STATUS_SUCCESS;}p = head;while(i--){p=p->next;}p->prior->next=new_node;new_node->prior=p->prior;p->prior=new_node;new_node->next=p;return STATUS_SUCCESS;}/* *delete method: delete a eType data from the dual link *return STATUS_SUCCESS if success*/Status delnode(du_node* head,eType e){du_node* p = search(head,e,equal);if(!head)return STATUS_ERROR;if(p){p->prior->next = p->next;p->next->prior = p->prior;free(p);return STATUS_SUCCESS;}return STATUS_NOT_FOUND;}/**find_prior: find the prior element *return element data.*/Status find_prior(du_node* head,eType cur_e,eType* ppe){du_node* p;if(!head)return STATUS_ERROR;p=head->next->next;//the second node.while(p!=head){if(p->data==cur_e){*ppe=p->prior->data;return STATUS_OK;}}return STATUS_NOT_FOUND;}/**find_next: find the next element* return element data.*/Status find_next(du_node* head,eTeype cur_e,eType* pne){du_node* p;if(!head)return STATUS_ERROR;p=head->next;//first node.while(p->next!=head){if(p->data==cur_e){*pne=p->next->data;return STATUS_OK;}}return STATUS_NOT_FOUND;}/**traversal: visit each element of the dual link.*/void traversal(du_node* head,void(*visit)(eType e)){du_node* p = head->next;if(!head)return;while(p!=head){visit(p->data);p=p->next;}}


链表的应用

java jdk中最为经典的链表结构要数LinkedList,LinkedList其实是带头结点的双向循环链表的实现。我们看看其主要的源码

public class LinkedList<E> extends AbstractSequentialList<E>    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{    private transient Entry<E> header = new Entry<E>(null, null, null);    private transient int size = 0;    /**     * Constructs an empty list.     */    public LinkedList() {        header.next = header.previous = header;    }......
从类的声明中不能明显看到循环双链表的结构,而只有一个Entry头元素,Entry是LinkedList的内部类,声明如下:

    private static class Entry<E> {E element;Entry<E> next;Entry<E> previous;Entry(E element, Entry<E> next, Entry<E> previous) {    this.element = element;    this.next = next;    this.previous = previous;}    }

这样看起来更像双向链表的结构,接下来再看下它是如何定位元素,添加元素,删除元素。

    private Entry<E> addBefore(E e, Entry<E> entry) {Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);newEntry.previous.next = newEntry;newEntry.next.previous = newEntry;size++;modCount++;return newEntry;    }    private E remove(Entry<E> e) {if (e == header)    throw new NoSuchElementException();        E result = e.element;e.previous.next = e.next;e.next.previous = e.previous;        e.next = e.previous = null;        e.element = null;size--;modCount++;        return result;    }......
这两个私有方法用来进行元素的添加和删除,添加和删除的方法和双链表的删除和增加相同,首先根据元素e创建Entry,在创建过程中就设置了新Entry的前驱和后继元素为entry.previous和entry,随后再将其前驱和后继Entry的后继和前驱分别设置为新的Entry。删除的方法更加明了,就不再赘述。源码中还有一个entry方法用来返回指定索引处指定的Entry元素,这里的索引是相对头元素的。我们看下这个方法:

    /**     * Returns the indexed entry.     */    private Entry<E> entry(int index) {        if (index < 0 || index >= size)            throw new IndexOutOfBoundsException("Index: "+index+                                                ", Size: "+size);        Entry<E> e = header;        if (index < (size >> 1)) {            for (int i = 0; i <= index; i++)                e = e.next;        } else {            for (int i = size; i > index; i--)                e = e.previous;        }        return e;    }

方法使用了一个聪明的方法来更快的得到Entry,首先判断index索引是否大于size的一半,如果是就按照next来找,否则通过previous来找,这么做的道理很简单,我们总能把搜索范围缩小到n/2,而不是整个链表n.


在STL中同样有关于链表的经典容器List,同linkedlist相似,它同样带有头结点,STL的源码晦涩难懂,这里我只做简单的介绍,说明链表在List容器中的实现原理。

template <class T>  struct _List_node_base {  _List_node_base* _M_next;  _List_node_base* _M_prev;};template <class _Tp>struct _List_node : public _List_node_base {  _Tp _M_data;};

这是SGI-STL (v3.3) 中List对于结点的定义。对于数据的定义采用了继承方式,区别于java内部类。

  iterator insert(iterator __position, const _Tp& __x) {    _Node* __tmp = _M_create_node(__x);    __tmp->_M_next = __position._M_node;    __tmp->_M_prev = __position._M_node->_M_prev;    __position._M_node->_M_prev->_M_next = __tmp;    __position._M_node->_M_prev = __tmp;    return __tmp;  }
这是List类中对于insert的实现部分,这里,我们不要试图去详细了解其中详细的内容和变量类型,不然会陷入无底洞,反而打乱我们的思维,我们的重点应该回到链表上来,对于这段代码,我们能够抽象出其表达的内容就足够了。这里我们用到容器的迭代器,迭代器可以看做是为容器而提供的一种访问数据的标准模型,可以看做是一种广义上的指针类型,可以指向容器的任意元素。一开始同样是创建新节点,然后通过迭代器来更新新节点前后指针,同时更新迭代器指代元素的指针。代码相比LinkedList实现更加清晰和
明了。对于删除元素的操作也是类似,如下:

  iterator erase(iterator __position) {    _List_node_base* __next_node = __position._M_node->_M_next;    _List_node_base* __prev_node = __position._M_node->_M_prev;    _Node* __n = (_Node*) __position._M_node;    __prev_node->_M_next = __next_node;    __next_node->_M_prev = __prev_node;    _Destroy(&__n->_M_data);    _M_put_node(__n);    return iterator((_Node*) __next_node);  }


或许,真的没有必要介绍这么多重复的东西,说到底都是链表的结构和基本的操作,以至于我不想在这里再介绍linux内核中的链表结构list_head,它的链表操作基本类似,不过很多地方实现的确精妙,有机会再做介绍。当然在List和LinkedList中的很多方法我也没多做介绍,比如 STL List如何实现sort,或者unique,如果你想知道更加详细的内容,那就下载一份源码看看吧!



SGI-STL 下载地址 http://www.sgi.com/tech/stl/download.html


0 0
原创粉丝点击