一文探索C#中实现双向链表的方法
作者:wenchm
一、涉及到的知识点
1.定义
在双向链表中,每个节点有两个指针域,一个指向它的前一个节点(即直接前驱),另一个指向它的后一个节点(即直接后继)。这种设计使得双向链表可以进行双向遍历,即可以从头节点开始向前遍历,也可以从尾节点开始向后遍历。
双向链表的节点结构通常如下所示:
struct Node { // 数据域 int data; // 指向直接前驱的指针 Node* prev; // 指向直接后继的指针 Node* next; };
2.双向链表与单向链表的区别
双向链表的算法描述和单向链表基本相同,但是双向链表在删除和插入节点时与单向链表有很大的不同:双向链表在删除节点时,不但要修改节点的直接后继指针,还要同时修改节点的直接前驱指针。在插入时更是要修改插入节点的前驱和后继的两个方向上的指针。
二、双向链表实现基础
为了让实现的链表具有更多的实用性,链表的参数的数据类型选择Objects。Objects参数可以是1个也可以2个甚至更多,总之为了表达双向链表更能为实际生活中的需要,这一步就要极尽所能地、契合实际需要地设计Objects的参数。
1.定义参数类Objects
/// <summary> /// 定义双向链表的参数类型是Objects类型 /// </summary> /// <param name="value">参数,数据数值</param> /// <param name="name">参数,数据名称</param> /// <param name="index">参数,数据索引</param> public class Objects(int value, string name, int index) { public int Value { get; set; } = value; public string Name { get; set; } = name; public int Index { get; set; } = index; }
2.定义节点类ListNode
// 定义结点类 public class ListNode(Objects obj) { public Objects _object = obj; public ListNode? _next; public ListNode? previous; public ListNode? Next { get { return _next; } set { _next = value; } } public Objects Object { get { return _object; } set { _object = value; } } }
3.定义链表类LinkedList及其方法
在链表类中要定义两个指针_head、_tail,用于正向、逆向查询;_current用于在链表类实时表达当前节点指针;
private ListNode? _head; private ListNode? _tail; private ListNode? _current;
链表中的方法是自定义的,是满足实际需要时因地制宜设计的方法。因此,需要什么方法,就在链表中设计什么方法。
在本例中,切记一切方法的参数类型都是Objects类型的。
(1)在链表类中定义Append方法、Print方法
public class LinkedList { //构造函数 public LinkedList() { _ListCountValue = 0; _head = null; _tail = null; } /// <summary> /// 链表名 /// </summary> private string listname = ""; public string ListName { get { return listname; } set { listname = value; } } /// <summary> /// 头指针 /// </summary> private ListNode? _head; /// <summary> /// 尾指针 /// </summary> private ListNode? _tail; /// <summary> /// 定义字段当前指针current /// 定义属性Current /// 字段是一种变量,属性是一种方法 /// </summary> private ListNode? _current; public ListNode? Current { get { return _current; } set { _current = value; } } // 定义链表数据的个数 private int _ListCountValue; /// <summary> /// 尾部添加数据 /// </summary> public void Append(Objects value) { ListNode newNode = new(value); if (IsNull()) //如果头指针为空 { _head = newNode; _tail = newNode; } else { _tail!.Next = newNode; // 原尾节点的下节点=新节点 newNode.previous = _tail;// 新节点的前节点=原尾结点 _tail = newNode; // 新尾结点=新节点 } _current = newNode; // 当前节点=新节点 _ListCountValue += 1; // 节点总数增加1 } /// <summary> /// 删除当前的数据 /// </summary> public void Delete() { if (!IsNull()) //若为空链表 { //删除头部 if (IsBof()) { _head = _current!.Next; _current = _head; _ListCountValue -= 1; return; } //删除尾 if (IsEof()) { _tail = _current!.previous; _tail!.Next = null; _current = _tail; _ListCountValue -= 1; return; } //若删除中间数据 _current!.previous!.Next = _current.Next; _current = _current.previous; _ListCountValue -= 1; return; } }
(2)在链表类中定义MoveFirst、MovePrevious、MoveNext、MoveTail、Clear、GetCurrentValue、IsNull、IsEof、IsBof、
/// <summary> /// 向后移动一个数据 /// </summary> public void MoveNext() { if (!IsEof()) _current = _current!.Next; } /// <summary> /// 向前移动一个数据 /// </summary> public void MovePrevious() { if (!IsBof()) _current = _current!.previous; } /// <summary> /// 移动到第一个数据 /// </summary> public void MoveFirst() { _current = _head; } /// <summary> /// 移动到最后一个数据 /// </summary> public void MoveTail() { _current = _tail; } /// <summary> /// 判断是否为空链表 /// </summary> public bool IsNull() { if (_ListCountValue == 0) return true; else return false; } /// <summary> /// 判断是否为到达尾部 /// Is End of File /// </summary> public bool IsEof() { if (_current == _tail) return true; else return false; } /// <summary> /// 判断是否为到达头部 /// Is Beginning of File /// </summary> public bool IsBof() { if (_current == _head) return true; else return false; } /// <summary> /// 获取当前节点数据 /// </summary> public Objects GetCurrentValue() { return _current!._object; } /// <summary> /// 取得链表的数据个数 /// </summary> public int ListCount { get { return _ListCountValue; } } /// <summary> /// 清空链表 /// </summary> public void Clear() { MoveFirst(); while (!IsNull()) { Delete(); } }
(3)在链表类中定义Insert()、InsertAscending()、InsertUnAscending()、SortList()
/// <summary> /// 对链表数据冒泡排序 /// </summary> public static ListNode? SortList(ListNode? head) { if (head == null || head.Next == null) { return head; } bool swapped; do { swapped = false; ListNode? current = head; while (current != null && current.Next != null) { if (current.Next.Object.Value < current.Object.Value) { (current.Next._object, current._object) = (current._object, current.Next._object); swapped = true; } current = current.Next; } } while (swapped); return head; } /// <summary> /// 在当前位置前插入数据 /// </summary> public void Insert(Objects value) { ListNode newNode = new(value); if (IsNull()) { //为空表,则添加 Append(value); return; } if (IsBof()) { //为头部插入 newNode.Next = _head; _head!.previous = newNode; _head = newNode; _current = _head; _ListCountValue += 1; return; } //中间插入 newNode.Next = _current; newNode.previous = _current!.previous; _current.previous!.Next = newNode; _current.previous = newNode; _current = newNode; _ListCountValue += 1; } /// <summary> /// 进行升序插入 /// </summary> public void InsertAscending(Objects value) { //为空链表 if (IsNull()) { Append(value); return; } //移动到头 MoveFirst(); if (value.Value < GetCurrentValue().Value) //满足条件,则插入,退出 { Insert(value); return; } while (true) { if (value.Value < GetCurrentValue().Value)//满足条件,则插入,退出 { Insert(value); break; } if (IsEof()) { Append(value); break; } MoveNext(); } } /// <summary> /// 进行非升序插入 /// </summary> public void InsertUnAscending(Objects value) { //为空链表 if (IsNull()) { Append(value); return; } //移动到头 MoveFirst(); if (value.Value > GetCurrentValue().Value)//满足条件,则插入,退出 { Insert(value); return; } while (true) { if (value.Value > GetCurrentValue().Value)//满足条件,则插入,退出 { Insert(value); break; } if (IsEof()) { Append(value); break; } MoveNext(); } }
(4)FindObjects(int value)、FindObjects(string name)、DeleteObjects(string name)、DeleteObjects(int value)
/// <summary> /// 根据数据名称查询数据 /// </summary> /// <param name="name"> /// 数据名称 /// </param> public Objects? FindObjects(string name) { ListNode? nodes = _head; if (IsNull()) { return null; } else if (IsEof()) { return null; } else while (nodes!._object.Name != name) { if (nodes.Next == null) { _current = nodes; return null; } nodes = nodes.Next; } _current = nodes; return nodes._object; } /// <summary> /// 根据参数值查询数据 /// </summary> /// <param name="value"> /// 数据编号 /// </param> public Objects? FindObjects(int value) { ListNode? nodes = _head; if (IsNull()) { return null; } else if (IsEof()) { return null; } else while (nodes!._object.Value != value) { if (nodes.Next == null) { _current = nodes; return null; } nodes = nodes.Next; } _current = nodes; return nodes._object; } /// <summary> /// 根据数据名称删除数据 /// </summary> /// <param name="name"> /// 数据名称 /// </param> public Objects? DeleteObjects(string name) { Objects? objects; objects = FindObjects(name); Delete(); return objects; } /// <summary> /// 根据参数值删除数据 /// </summary> /// <param name="value"> /// 数据编号 /// </param> public Objects? DeleteObjects(int value) { Objects? objects; objects = FindObjects(value); Delete(); return objects; } public void Print() { ListNode? current = _head; while (current != null) { Console.WriteLine(current._object.Value); current = current.Next; } } public void Print() { ListNode? current = _head; while (current != null) { Console.WriteLine(current._object.Value); current = current.Next; } } }
三、一些Objects类对象操作技巧
1.Objects类对象用于比较
Objects类对象是复合类型的数据,本身不能用于比较,但是其int类型的属性值是可以进行比较的。比如,应该使用“Value”属性来访问对象的整数值。
ListNode newNode = new(obj); if (_head == null || _head.Object.Value >= obj.Value) { newNode.Next = _head; return newNode; } else { ListNode? previous = _head; ListNode? current = _head!.Next; while (current != null && current.Object.Value < obj.Value) { previous = current; current = current!.Next; } newNode!.Next = current; previous!.Next = newNode; }
if (_head != null) { return _current!.Object.Value; } else { return default; // 或者 return 0; }
while (current != null && current.Next != null) { if (current.Next.Object.Value < current.Object.Value) { (current.Next.Object.Value, current.Object.Value) = (current.Object.Value, current.Next.Object.Value); swapped = true; } current = current.Next; }
2. Objects类对象用于链表的初始化
Objects类对象是复合类型的数据,用于链表初始化时要复杂一些,不再向单纯的int、string类型赋值得那么简单直接。
Objects类对象用于链表初始化要体现 Objects类的定义,实参和形参的数量和类型要一一对应。
LinkedList linkedList = new(); linkedList.Append(new Objects(5, "Five", 1)); linkedList.Append(new Objects(2, "Two", 2)); linkedList.Append(new Objects(8, "Eight", 3)); linkedList.Append(new Objects(1, "One", 4));
四、实例Main()
在实例的双向链表类中,设计一个Append方法向链表的末尾追加初始数据5,2,8,1。然后用Print方法显示链表数据。
链表类中的其他方法的示例,详见Main方法。
class Program { static void Main(string[] args) { ArgumentNullException.ThrowIfNull(args); LinkedList list = new(); // 插入结点 list.Append(new Objects(5, "five", 0)); list.Append(new Objects(2, "two", 1)); list.Append(new Objects(8, "eight", 2)); list.Append(new Objects(1, "one", 3)); list.Append(new Objects(3, "three", 4)); // 获取当前结点的值 Console.Write("当前结点的值:"); Console.WriteLine(list.GetCurrentValue().Value); // 移动到第一个结点 list.MoveFirst(); Console.Write("第一结点的值:"); Console.WriteLine(list.GetCurrentValue().Value); // 移动到下一个结点 list.MoveNext(); Console.Write("下一结点的值:"); Console.WriteLine(list.GetCurrentValue().Value); // 移动到上一个结点 list.MovePrevious(); Console.Write("上一结点的值:"); Console.WriteLine(list.GetCurrentValue().Value); list.Print(); Console.WriteLine("*初始数据*"); // 删除尾首2个结点 list.MoveTail(); list.Delete(); list.MoveFirst(); list.Delete(); list.Print(); Console.WriteLine("*删除节点*"); // 插入升序结点 LinkedList.SortList(list._head);//先排序 list.InsertAscending(new Objects(6, "six", 5)); list.InsertAscending(new Objects(4, "four", 6)); list.InsertAscending(new Objects(9, "none", 7)); list.Print(); Console.WriteLine("*升序插入*"); // 插入非升序结点 list.InsertUnAscending(new Objects(7, "seven", 8)); list.InsertUnAscending(new Objects(3, "three", 9)); list.InsertUnAscending(new Objects(10, "ten", 10)); list.Print(); Console.WriteLine("*非升序插入*"); // 清空链表 list.Clear(); list.Print(); Console.WriteLine(); Console.WriteLine("**清空就没有了**"); // 插入数据 list.Insert(new Objects(0, "zero", 11)); list.Insert(new Objects(1, "one", 12)); list.Insert(new Objects(2, "two", 13)); list.Insert(new Objects(3, "three", 14)); list.Print(); Console.WriteLine("*新数据*"); Console.WriteLine(list.GetCurrentValue().Value); Console.WriteLine("*最后插入的是当前值*"); list.MoveFirst(); Console.WriteLine(list.GetCurrentValue().Value); Console.WriteLine("*当前值居然是头节点*"); list.Insert(new Objects(5, "five", 15)); list.Print(); Console.WriteLine("*在当前值前面插入5*"); list.MoveNext(); list.Insert(new Objects(6, "six", 16)); list.Print(); Console.WriteLine("*在当前节点的下节点前面插入6*"); list.MoveFirst(); list.MovePrevious(); list.Insert(new Objects(7, "seven", 17)); list.Print(); Console.WriteLine("*头节点没有前节点故在头节点前面插入7*"); list.FindObjects(3); Console.WriteLine(list.GetCurrentValue().Value); Console.WriteLine("*按数据值3查找*"); list.FindObjects("three"); Console.WriteLine(list.GetCurrentValue().Value); Console.WriteLine("*按参数名称three查找*"); list.DeleteObjects(3); list.Print(); Console.WriteLine("*已经删除了值=3的参数*"); list.DeleteObjects("six"); list.Print(); Console.WriteLine("*已经删除了名称=six的参数*"); } }
运行结果:
当前结点的值:3
第一结点的值:5
下一结点的值:2
上一结点的值:5
5
2
8
1
3
*初始数据*
2
8
1
*删除节点*
1
2
4
6
8
9
*升序插入*
10
7
3
1
2
4
6
8
9
*非升序插入*
**清空就没有了**
3
2
1
0
*新数据*
3
*最后插入的是当前值*
3
*当前值居然是头节点*
5
3
2
1
0
*在当前值前面插入5*
5
6
3
2
1
0
*在当前节点的下节点前面插入6*
7
5
6
3
2
1
0
*头节点没有前节点故在头节点前面插入7*
3
*按数据值3查找*
3
*按参数名称three查找*
7
5
6
2
1
0
*已经删除了值=3的参数*
7
5
2
1
0
*已经删除了名称=six的参数*
到此这篇关于一文探索C#中实现双向链表的方法的文章就介绍到这了,更多相关C#双向链表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!