您的位置:首页 > 理论基础 > 数据结构算法

BDS之链表

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

静态链表

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

#define LIST_SIZE   10
typdef 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 _NODEBUG
printf("FILE:%s LINE:%d,error msg(code=%d),out of mem in initial method.\n",__FILE__,__LINE__,OUT_OF_MEM);
#endif
return 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 node
while(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 _NODEBUG
printf("FILE:%s LINE:%d,error msg(code=%d),error msg(code=%d):out of mem in initial method.\n",\
__FILE__,__LINE__,OUT_OF_MEM);
#endif
return 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息