C#数据结构之双向链表(DbLinkList)实例详解

作者:Jimmy.Yang 时间:2023-08-23 08:56:44 

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

这是继上一篇《C#数据结构之单链表(LinkList)实例详解》的继续,对于双向链接,节点上除了Next属性外,还要有Prev属性用来指向前一个节点,DbNode定义如下:


namespace 线性表
{
 public class DbNode<T>
 {
   private T data;
   private DbNode<T> prev;
   private DbNode<T> next;
   public DbNode(T data, DbNode<T> next,DbNode<T> prev)
   {
     this.data = data;
     this.next = next;
     this.prev = prev;
   }
   public DbNode(T data, DbNode<T> next)
   {
     this.data = data;
     this.next = next;
     this.prev = null;
   }
   public DbNode(DbNode<T> next)
   {
     this.data = default(T);
     this.next = next;
     this.prev = null;
   }
   public DbNode(T data)
   {
     this.data = data;
     this.next = null;
     this.prev = null;
   }
   public DbNode()
   {
     data = default(T);
     next = null;
     prev = null;
   }
   public T Data
   {
     set { this.data = value; }
     get { return this.data; }
   }
   public DbNode<T> Prev
   {
     get { return prev; }
     set { prev = value; }
   }
   public DbNode<T> Next
   {
     get { return next; }
     set { next = value; }
   }
 }
}

双链表的插入操作要稍微复杂一点,示意图如下:

C#数据结构之双向链表(DbLinkList)实例详解

同样对于删除操作,也要额外处理prev指向

C#数据结构之双向链表(DbLinkList)实例详解

完整实现DbLinkList<T>:


using System;
using System.Text;
namespace 线性表
{
 public class DbLinkList<T> : IListDS<T>
 {
   private DbNode<T> head;
   public DbNode<T> Head
   {
     get { return head; }
     set { head = value; }
   }
   public DbLinkList()
   {
     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()
   {
     DbNode<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)
   {
     DbNode<T> d = new DbNode<T>(item);
     DbNode<T> n = new DbNode<T>();
     if (head == null)
     {
       head = d;
       return;
     }
     n = head;
     while (n.Next != null)
     {
       n = n.Next;
     }
     n.Next = d;
     d.Prev = n;
   }
   //前插
   public void InsertBefore(T item, int i)
   {
     if (IsEmpty() || i < 0)
     {
       Console.WriteLine("List is empty or Position is error!");
       return;
     }
     //在最开头插入
     if (i == 0)
     {
       DbNode<T> q = new DbNode<T>(item);
       q.Next = head;//把"头"改成第二个元素
       head.Prev = q;
       head = q;//把自己设置为"头"
       return;
     }
     DbNode<T> n = head;
     DbNode<T> d = new DbNode<T>();
     int j = 0;
     //找到位置i的前一个元素d
     while (n.Next != null && j < i)
     {
       d = n;
       n = n.Next;
       j++;
     }
     if (n.Next == null) //说明是在最后节点插入(即追加)
     {
       DbNode<T> q = new DbNode<T>(item);
       n.Next = q;
       q.Prev = n;
       q.Next = null;
     }
     else
     {
       if (j == i)
       {
         DbNode<T> q = new DbNode<T>(item);
         d.Next = q;
         q.Prev = d;
         q.Next = n;
         n.Prev = q;
       }
     }
   }
   /// <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)
     {
       DbNode<T> q = new DbNode<T>(item);
       q.Next = head.Next;
       head.Next.Prev = q;
       head.Next = q;
       q.Prev = head;
       return;
     }
     DbNode<T> p = head;
     int j = 0;
     while (p != null && j < i)
     {
       p = p.Next;
       j++;
     }
     if (j == i)
     {
       DbNode<T> q = new DbNode<T>(item);
       q.Next = p.Next;
       if (p.Next != null)
       {
         p.Next.Prev = q;
       }
       p.Next = q;
       q.Prev = p;
     }
     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);
     }
     DbNode<T> q = new DbNode<T>();
     if (i == 0)
     {
       q = head;
       head = head.Next;
       head.Prev = null;
       return q.Data;
     }
     DbNode<T> p = head;
     int j = 0;
     while (p.Next != null && j < i)
     {
       j++;
       q = p;
       p = p.Next;
     }
     if (j == i)
     {
       p.Next.Prev = q;
       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);
     }
     DbNode<T> p = new DbNode<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;
     }
     DbNode<T> p = new DbNode<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()
   {
     DbLinkList<T> result = new DbLinkList<T>();
     DbNode<T> t = this.head;
     result.Head = new DbNode<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能直接回收
   }
   //得到某个指定的节点(为了下面测试从后向前遍历)
   private DbNode<T> GetNodeAt(int i){
     if (IsEmpty())
     {
       Console.WriteLine("List is empty!");
       return null;
     }
     DbNode<T> p = new DbNode<T>();
     p = head;
     if (i == 0)
     {
       return p;
     }
     int j = 0;
     while (p.Next != null && j < i)
     {
       j++;
       p = p.Next;
     }
     if (j == i)
     {
       return p;
     }
     else
     {
       Console.WriteLine("The node is not exist!");
       return null;
     }
   }
   /// <summary>
   /// 测试用prev属性从后面开始遍历
   /// </summary>
   /// <returns></returns>
   public string TestPrevErgodic()
   {
     DbNode<T> tail = GetNodeAt(Count() - 1);
     StringBuilder sb = new StringBuilder();
     sb.Append(tail.Data.ToString() + ",");
     while (tail.Prev != null)
     {
       sb.Append(tail.Prev.Data.ToString() + ",");
       tail = tail.Prev;
     }
     return sb.ToString().TrimEnd(',');      
   }
   public override string ToString()
   {
     StringBuilder sb = new StringBuilder();
     DbNode<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(',');
   }
 }
}

测试代码片段:


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

当然从上面的测试代码中,似乎并不能看出双链表的优点,双链表的好处在于,如果需要在链表中,需要通过某个节点得到它的前驱节点时,双链表直接用prev属性就能找到;而单链表要做到这一点,必须再次从Head节点开始一个一个用Next向下找,这样时间复杂度从O(n)降到O(1),显然更有效率。

注:如果把双链表再做一下改造,让头尾接起来,即Head的Prev属性指向最后一个节点(就叫做Tail吧),同时把Tail节点的Next属性指向Head节点,就形成了所谓的“循环双向链表

C#数据结构之双向链表(DbLinkList)实例详解

当然,这样的结构可以在链表中再增加一个Tail节点属性,在做元素插入或删除时,可以循环到底以更新尾节点Tail(当然这样会给插入/删除元素带来一些额外的开销),但是却可以给GetItemAt(int i)方法带来优化的空间,比如要查找的元素在前半段时,可以从Head开始用next向后找;反之,如果要找的元素在后半段,则可以从Tail节点用prev属性向前找。

注:.Net中微软已经给出了一个内置的双向链表System.Collections.Generic.LinkedList<T>,在了解双链表的原理后,建议大家直接系统内置的链表。

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

标签:C#,数据结构
0
投稿

猜你喜欢

  • Flutter中http请求抓包的完美解决方案

    2023-08-22 18:47:47
  • Android Studio3安装图文教程

    2022-03-21 04:26:44
  • Android入门教程之Fragment的具体使用详解

    2021-09-30 01:21:57
  • Java ByteBuffer网络编程用法实例解析

    2022-09-17 20:16:22
  • C# SMTP发送邮件的示例

    2021-06-20 12:36:10
  • C#特性 匿名类型与隐式类型局部变量使用介绍

    2023-09-29 12:42:50
  • spring cglib 与 jdk 动态代理

    2021-07-19 20:28:43
  • 解决idea中maven项目无端显示404错误的方法

    2023-02-12 23:48:44
  • Android编程显示网络上的图片实例详解

    2021-11-29 05:21:10
  • Java ArrayList扩容问题实例详解

    2022-05-08 08:57:57
  • java 教你如何给你的头像添加一个好看的国旗

    2021-11-11 02:53:25
  • C#获取CPU编号的方法

    2022-01-05 10:57:37
  • Mybatis中一条SQL使用两个foreach的问题及解决

    2022-02-04 06:05:41
  • SpringCloud微服务架构实战之微服务治理功能的实现

    2023-07-20 09:06:38
  • java打印指定年月的日历

    2023-11-11 19:21:19
  • 利用Java计算某个日期是星期几

    2023-11-17 05:49:42
  • Android 单双击实现的方法步骤

    2023-04-19 02:19:31
  • java 数组越界判断和获取数组长度的实现方式

    2023-05-24 07:06:52
  • Android中Notification用法实例总结

    2023-03-16 23:05:38
  • 详解spring mvc中url-pattern的写法

    2023-11-11 07:30:58
  • asp之家 软件编程 m.aspxhome.com