代码改变世界

链表

2011-01-04 10:24  通心菜  阅读(648)  评论(0编辑  收藏  举报

   
    链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
    由于链表这种数据结构必须利用指针变量才能实现,因此先介绍指针的概念。
    由前述可知,计算机的内存储器被划分为一个个的存储单元,每个存储单元存放8个二进制位(即一个字节)。存储单元按一定的规则编号,这个编号就是存储单元的地址。也就是说计算机中存储的每个字节是一个基本内存单元,有一个地址。计算机就是通过这种地址编号的方式来管理内存数据读写的准确定位的。
    计算机是如何从内存单元中存取数据的呢?从程序设计的角度看,有两种办法:一是通过变量名;二是通过地址。程序中声明的变量是要占据一定的内存空间的,例如,C语言中整型变量占2字节,实型变量占4字节。程序中定义的变量在程序运行时被分配内存空间。在变量分配内存空间的同时,变量名也就成为了相应内存空间的名称,在程序中可以用这个名字访问该内存空间,表现在程序语句中就是通过变量名存取变量内容(这就是程序中定义变量的用途,即程序中通过定义变量来实现数据在内存中的存取)。但是,有时使用变量名不够方便或者根本没有变量名可用,这时就可以直接用地址来访问内存单元。例如,学生公寓中每个学生住一间房,每个学生就相当于一个变量的内容,变量名指定为学生姓名,房间是存储单元,房号就是存储单元地址。如果知道了学生姓名,可以通过这个名字来访问该学生,这相当于使用变量名访问数据。如果知道了房号,同样也可以访问该学生,这相当于通过地址访问数据。
    由于通过地址能访问指定的内存存储单元,因此可以说,地址“指向”该内存存储单元(如同说,房间号“指向”某一房间一样)。故将地址形象化地称为“指针”,意思是通过它能找到以它为地址的内存单元一个变量的地址称为该变量的“指针”。如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为“指针变量”。在许多高级程序设计语言中有专门用来存放内存单元地址的变量类型,这就是指针类型。指针变量就是具有指针类型的变量,它是用于存放内存单元地址的。
    通过变量名访问一个变量是直接的,而通过指针访问一个变量是间接的。就好像要在学生公寓中找一位学生,不知道他的姓名,也不知道他住哪一间房,但是知道101房间里有他的地址,走进101房间后看到一张字条:“ 找我请到302”,这时按照字条上的地址到302去,便顺利地找到了他。这个101房间,就相当于一个指针变量,字条上的字便是指针变量中存放的内容(另一个内存单元的地址),而住在302房间的学生便是指针所指向的内容。
    指针作为维系结点的纽带,可以通过它实现链式存储。假设有五个学生某一门功课的成绩分别为A、B、C、D和E,这五个数据在内存中的存储单元地址分别为1248、1488、1366、1022和1520,其链表结构如图5.4所示。

        
                        
图5.4链表示意图

    链表有一个“头指针”变量,图5.4中以 head表示,它存放一个地址,该地址指向链表中第一个结点,第一个结点又指向第二个结点……直到最后一个结点。该结点不再指向其他结点,它称为“表尾”,它的地址部分存放一个“NULL”(表示“空地址”),链表到此结束。链表中每个结点都包括两个部分:用户需要用的实际数据和下一个结点的地址。
    可以看到链表中各结点在内存中可以不是连续存放的。要找到某一结点C,必须先找到其上一个结点B,根据结点B提供的下一个结点地址才能找到C。链表有一个“头指针”,因此通过“头指针”可以按顺序往下找到链表中的任一结点,如果不提供“头指针”,则整个链表都无法访问。链表如同一条铁链一样,一环扣一环,中间是不能断开的。打个比方,幼儿园的老师带领孩子出来散步,老师(作为头指针)牵着第一个小孩的手,第一个小孩的另一只手牵着第二个孩子……这就是一个“链”,最后一个孩子有一只手空着,他是“链尾”。要找这个队伍,必须先找到老师,然后按顺序找到每一个孩子。
    图5.4的链表每个结点中只有一个指向后继结点的指针,该链表称为单链表。其实结点中可以有不止一个用于链接其他结点的指针。如果每个结点中有两个用于链接其他结点的指针,一个指向前趋结点(称前趋指针),另一个指向后继结点(称后继指针),则构成双向链表。双向链表如图5.5所示。

       

                            图5.5双向链表示意图
     双向链表的插入和删除动态图:

        双向链表的删除    

        双向链表的插入

     链表的一个重要特点是插入、删除操作灵活方便,不需移动结点,只需改变结点中指针域的值即可。而数组由于用存储单元的邻接性体现数组中元素的逻辑顺序关系,因此对数组进行插入和删除运算时,可能需要移动大量的元素,以保持这种物理和逻辑的一致性。如数组中有m个元素,往第i(i<m)个元素后面插入一个新元素,需要将第i+1个元素至第m个元素共m-i个元素向后移动。
    单向链表的插入和删除动态图:

      单向链表的插入

       单向链表的删除    

    由于链表的插入、删除操作灵活方便,我们可以在程序运行期间根据需
要动态申请内存,为新结点分配存储空间,并通过修改指针将新结点链接到链表上。因此,链表是一种动态数据结构。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LinkedList
{

public class Node<T>
{
/// <summary>
/// 节点数据
/// </summary>
public T NodeData { get; set; }
/// <summary>
/// 指向下一个节点
/// </summary>
public Node<T> Next { get; set; }
}



public class SingleList<T>
{
private Node<T> _first; //头节点
private Node<T> _last; //尾节点
private int _length; //长度


public T First
{
get { return _first.NodeData; }
}

public T Last
{
get { return _last.NodeData; }
}

/// <summary>
/// 长度
/// </summary>
public int Length
{
get { return _length; }
}

/// <summary>
/// 是否为空
/// </summary>
public bool IsEmpty
{
get { return _first == null; }
}

/// <summary>
/// 清空链表
/// </summary>
public void Clear()
{
_first
= null;
}


/// <summary>
/// 追加一个节点
/// </summary>
/// <param name="item"></param>
public void Append(T item)
{
Node
<T> NewNode = new Node<T>();
NewNode.NodeData
= item;
if (_first == null)
{
_first
= _last = NewNode;
}
else
{
_last.Next
= NewNode;
_last
= NewNode;
}
_length
++;
}


/// <summary>
/// 在指定位置插入一个节点
/// </summary>
/// <param name="item">T</param>
/// <param name="location">int</param>
public void Insert(T item, int location)
{
//Node<T> tempNodePre;//前一个
//Node<T> tempNodeNex;//后一个
Node<T> tempNode;//当前
Node<T> newNode = new Node<T>(); //待添加的节点

newNode.NodeData
= item;

if (this.IsEmpty)
{
throw new Exception("链表为空");
}

//在头位置插入
if (location == 0)
{
tempNode
= _first;
_first
= newNode;
newNode.Next
= tempNode;
_length
++;
}

//在尾插入
if (location == _length - 1)
{
_last.Next
= newNode;
_last
= newNode;
_length
++;
}

//其它位置
tempNode = this.FindNode(location);
if (tempNode == null)
{
throw new Exception("位置无效");
}
newNode.Next
= tempNode.Next;
tempNode.Next
= newNode;
_length
++;

}


public T Find(int index)
{
if (this.IsEmpty)
{
throw new Exception("链表为空");
}
Node
<T> find = this.FindNode(index);
if (find == null)
{
return default(T);
}
return this.FindNode(index).NodeData;
}


/// <summary>
/// 根据索引找到节点
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
private Node<T> FindNode(int index)
{
if (index < 0 || index >= _length)
{
return null;
}
int i = 0;
Node
<T> tempNode = _first;
while (i < index)
{
tempNode
= tempNode.Next;
i
++;
}
return tempNode;
}

}



class Program
{
static void Main(string[] args)
{
SingleList
<int> test = new SingleList<int>();
test.Append(
1);
test.Append(
2);
test.Append(
4);
test.Insert(
3, 2);
test.Append(
5);
test.Append(
6);

}
}
}