C#数据结构之单链表(LinkList)实例详解

 更新时间:2015年11月27日 12:25:28   作者:Jimmy.Yang  
这篇文章主要介绍了C#数据结构之单链表(LinkList)实现方法,结合实例形式较为详细的分析了单链表的原理、定义与C#具体实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下

本文实例讲述了C#数据结构之单链表(LinkList)实现方法。分享给大家供大家参考,具体如下:

这里我们来看下“单链表(LinkList)”。在上一篇《C#数据结构之顺序表(SeqList)实例详解》的最后,我们指出了:顺序表要求开辟一组连续的内存空间,而且插入/删除元素时,为了保证元素的顺序性,必须对后面的元素进行移动。如果你的应用中需要频繁对元素进行插入/删除,那么开销会很大。

而链表结构正好相反,先来看下结构:

每个元素至少具有二个属性:data和next。data用来存放数据,而next用来指出它后面的元素是谁(有点“指针”的意思)。

链表中的元素,通常也称为节点Node,下面是泛型版本的Node.cs

namespace 线性表
{
  public class Node<T>
  {
    private T data;
    private Node<T> next;
    public Node(T val, Node<T> p) 
    {
      data = val;
      next = p;
    }
    public Node(Node<T> p) 
    {
      next = p;
    }
    public Node(T val) 
    {
      data = val;
      next = null;
    }
    public Node() 
    {
      data = default(T);
      next = null;
    }
    public T Data 
    {
      get { return data; }
      set { data = value; }
    }
    public Node<T> Next 
    {
      get { return next; }
      set { next = value; }
    }
  }
}

链表在存储上并不要求所有元素按顺序存储,因为用节点的next就能找到下一个节点,这好象一根“用珠子串成的链子”,要找到其中的某一颗珠子,只要从第一颗节点(通常称为Head节点)开始,不断根据next指向找到下一个,直到找到需要的节点为止。

链表中需要有一个Head节点做为开始,这跟顺序表有所不同,下面是单链表的实现:

using System;
using System.Text;
namespace 线性表
{
  public class LinkList<T> : IListDS<T>
  {
    private Node<T> head;
    public Node<T> Head
    {
      get { return head; }
      set { head = value; }
    }
    public LinkList()
    {
      head = null;
    }
    /// <summary>
    /// 类索引器
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public T this[int index] 
    {
      get
      {
        return this.GetItemAt(index);
      }
    }
    /// <summary>
    /// 返回单链表的长度
    /// </summary>
    /// <returns></returns>
    public int Count()
    {
      Node<T> p = head;
      int len = 0;
      while (p != null)
      {
        len++;
        p = p.Next;
      }
      return len;
    }
    /// <summary>
    /// 清空
    /// </summary>
    public void Clear()
    {
      head = null;
    }
    /// <summary>
    /// 是否为空
    /// </summary>
    /// <returns></returns>
    public bool IsEmpty()
    {
      return head == null;
    }
    /// <summary>
    /// 在最后附加元素
    /// </summary>
    /// <param name="item"></param>
    public void Append(T item)
    {
      Node<T> d = new Node<T>(item);
      Node<T> n = new Node<T>();
      if (head == null)
      {
        head = d;
        return;
      }
      n = head;
      while (n.Next != null)
      {
        n = n.Next;
      }
      n.Next = d;
    }
    //前插
    public void InsertBefore(T item, int i)
    {
      if (IsEmpty() || i < 0)
      {
        Console.WriteLine("List is empty or Position is error!");
        return;
      }
      //在最开头插入
      if (i == 0)
      {
        Node<T> q = new Node<T>(item);
        q.Next = Head;//把"头"改成第二个元素
        head = q;//把自己设置为"头"
        return;
      }
      Node<T> n = head;
      Node<T> d = new Node<T>();
      int j = 0;
      //找到位置i的前一个元素d
      while (n.Next != null && j < i)
      {
        d = n;
        n = n.Next;
        j++;
      }
      if (n.Next == null) //说明是在最后节点插入(即追加)
      {
        Node<T> q = new Node<T>(item);
        n.Next = q;
        q.Next = null;
      }
      else
      {
        if (j == i)
        {
          Node<T> q = new Node<T>(item);
          d.Next = q;
          q.Next = n;
        }
      }
    }
    /// <summary>
    /// 在位置i后插入元素item
    /// </summary>
    /// <param name="item"></param>
    /// <param name="i"></param>
    public void InsertAfter(T item, int i)
    {
      if (IsEmpty() || i < 0)
      {
        Console.WriteLine("List is empty or Position is error!");
        return;
      }
      if (i == 0)
      {
        Node<T> q = new Node<T>(item);
        q.Next = head.Next;
        head.Next = q;
        return;
      }
      Node<T> p = head;
      int j = 0;
      while (p != null && j < i)
      {
        p = p.Next;
        j++;
      }
      if (j == i)
      {
        Node<T> q = new Node<T>(item);
        q.Next = p.Next;
        p.Next = q;
      }
      else
      {
        Console.WriteLine("Position is error!");
      }
    }
    /// <summary>
    /// 删除位置i的元素
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    public T RemoveAt(int i)
    {
      if (IsEmpty() || i < 0)
      {
        Console.WriteLine("Link is empty or Position is error!");
        return default(T);
      }
      Node<T> q = new Node<T>();
      if (i == 0)
      {
        q = head;
        head = head.Next;
        return q.Data;
      }
      Node<T> p = head;
      int j = 0;
      while (p.Next != null && j < i)
      {
        j++;
        q = p;
        p = p.Next;
      }
      if (j == i)
      {
        q.Next = p.Next;
        return p.Data;
      }
      else
      {
        Console.WriteLine("The node is not exist!");
        return default(T);
      }
    }
    /// <summary>
    /// 获取指定位置的元素
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    public T GetItemAt(int i)
    {
      if (IsEmpty())
      {
        Console.WriteLine("List is empty!");
        return default(T);
      }
      Node<T> p = new Node<T>();
      p = head;
      if (i == 0) 
      { 
        return p.Data; 
      }
      int j = 0;
      while (p.Next != null && j < i)
      {
        j++;
        p = p.Next;
      }
      if (j == i)
      {
        return p.Data;
      }
      else
      {
        Console.WriteLine("The node is not exist!");
        return default(T);
      }
    }
    //按元素值查找索引
    public int IndexOf(T value)
    {
      if (IsEmpty())
      {
        Console.WriteLine("List is Empty!");
        return -1;
      }
      Node<T> p = new Node<T>();
      p = head;
      int i = 0;
      while (!p.Data.Equals(value) && p.Next != null)
      {
        p = p.Next;
        i++;
      }
      return i;
    }
    /// <summary>
    /// 元素反转
    /// </summary>
    public void Reverse()
    {
      LinkList<T> result = new LinkList<T>();
      Node<T> t = this.head;
      result.Head = new Node<T>(t.Data);
      t = t.Next;
      //(把当前链接的元素从head开始遍历,逐个插入到另一个空链表中,这样得到的新链表正好元素顺序跟原链表是相反的)
      while (t!=null)
      {        
        result.InsertBefore(t.Data, 0);
        t = t.Next;
      }
      this.head = result.head;//将原链表直接挂到"反转后的链表"上
      result = null;//显式清空原链表的引用,以便让GC能直接回收
    }
    public override string ToString()
    {
      StringBuilder sb = new StringBuilder();
      Node<T> n = this.head;
      sb.Append(n.Data.ToString() + ",");
      while (n.Next != null)
      {
        sb.Append(n.Next.Data.ToString() + ",");
        n = n.Next;
      }
      return sb.ToString().TrimEnd(',');
    }
  }
}

下面是单链表插入和删除的算法图解:

可以看到:链表在元素插入/删除时,无需对后面的元素进行移动,只需要修改自身以及相邻节点的next指向即可,所以插入/删除元素的开销要比顺序表小得多。但是也应该注意到,其它操作比如:查找元素,反转倒置链表等,有可能需要遍历整个链表中的所有元素。

测试代码片断:

Console.WriteLine("-------------------------------------");
Console.WriteLine("单链表测试开始...");
LinkList<string> link = new LinkList<string>();
link.Head = new Node<string>("x");
link.InsertBefore("w", 0);
link.InsertBefore("v", 0);
link.Append("y");
link.InsertBefore("z", link.Count());
Console.WriteLine(link.Count());//5
Console.WriteLine(link.ToString());//v,w,x,y,z
Console.WriteLine(link[1]);//w
Console.WriteLine(link[0]);//v
Console.WriteLine(link[4]);//z
Console.WriteLine(link.IndexOf("z"));//4
Console.WriteLine(link.RemoveAt(2));//x
Console.WriteLine(link.ToString());//v,w,y,z
link.InsertBefore("x", 2);
Console.WriteLine(link.ToString());//v,w,x,y,z
Console.WriteLine(link.GetItemAt(2));//x
link.Reverse();
Console.WriteLine(link.ToString());//z,y,x,w,v
link.InsertAfter("1", 0);
link.InsertAfter("2", 1);
link.InsertAfter("6", 5);
link.InsertAfter("8", 7);
link.InsertAfter("A", 10);//Position is error!
Console.WriteLine(link.ToString()); //z,1,2,y,x,w,6,v,8

至于具体在实际应用中应该选用顺序表 or 链表,主要是看:对于元素插入/删除的频繁程度以及对于内存分配的苛刻程度。 如果不要求一开始就分配一组连续的内存区域,可以根据元素的增加而自动加大内存的使用量,或者插入/删除的次数很多,那么建议使用链表,反之用顺序表。

最后指出:可以给节点再添加一个prev元素,用于指出前一个节点是谁,即同时有next和prev二个指向,这种改进后的链表称为“双向链表”,它有助于某些情况下减少遍历循环的次数,本文中的这种仅有一个next指向的链表,称为“单链表”。

希望本文所述对大家C#程序设计有所帮助。

相关文章

  • C#中Equals和GetHashCode使用及区别

    C#中Equals和GetHashCode使用及区别

    这篇文章主要介绍了C#中Equals和GetHashCode使用及区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C#实现公式计算验证码的示例详解

    C#实现公式计算验证码的示例详解

    现在很多的平台已经不使用普通的数字、字母等验证码了,取而代之的是拼图类、选图类、旋转类或者计算类的验证码。本文将利用C#实现一个公式计算验证码,感兴趣的可以了解一下
    2022-10-10
  • C#中const用法详解

    C#中const用法详解

    这篇文章主要介绍了C#中const用法,实例分析了C#中const的用法及使用技巧,并对比分析了readonly关键字与const关键字的不同,需要的朋友可以参考下
    2014-11-11
  • 在C#中List集合使用First()方法获取第一个元素的操作

    在C#中List集合使用First()方法获取第一个元素的操作

    这篇文章主要介绍了在C#中List集合使用First()方法获取第一个元素的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • C#实现Excel数据导入到SQL server数据库

    C#实现Excel数据导入到SQL server数据库

    这篇文章主要为大家详细介绍了在C#中如何实现Excel数据导入到SQL server数据库中,文中的示例代码简洁易懂,希望对大家有一定的帮助
    2024-03-03
  • C#将指定目录所有文件名转换成小写的方法

    C#将指定目录所有文件名转换成小写的方法

    这篇文章主要介绍了C#将指定目录所有文件名转换成小写的方法,涉及C#中目录的遍历及filename.ToLowerInvariant方法的使用技巧,非常具有实用价值,需要的朋友可以参考下
    2015-04-04
  • C#中多维数组[,]和交错数组[][]的区别

    C#中多维数组[,]和交错数组[][]的区别

    这篇文章介绍了C#中多维数组[,]和交错数组[][]的区别,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • C# 中DateTime 的使用技巧汇总

    C# 中DateTime 的使用技巧汇总

    本文主要介绍了DateTime的使用技巧,包括获取本周、上周、下周、本月、上个月、下个月、本季度、上季度、下季度、本年度、上年度、下年度等多个方法,分享给大家,希望对大家有所帮助。
    2016-04-04
  • C#获取两个数的最大公约数和最小公倍数示例

    C#获取两个数的最大公约数和最小公倍数示例

    本文介绍了使用C#获取两个数的最大公约数和最小公倍数的示例,大家参考使用吧
    2014-01-01
  • 基于Aforge摄像头调用简单实例

    基于Aforge摄像头调用简单实例

    这篇文章主要为大家详细介绍了基于Aforge摄像头调用的简单实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-10-10

最新评论