1、简介

链表是一种非常基础的数据结构之一,我们在日常开发种都会接触到或者是接触到相同类型的链表数据结构.所以本文会使用C#算法来实现一个简单的链表数据结构,并实现其中几个简单的api以供使用.

2、概述

链表是一种递归的数据结构,他或者为null,或者是指向像一个节点的(node)的引用,该节点含有一个泛型的元素(当然可以是非泛型的,但是为了充分利用C#的优势,切让链表更具有灵活性,这里使用泛型)和指向另一个链表的引用.

3、实战 单向链表

如下图,因为下一个节点对象没有保持上个节点的引用,所以这种链表称之为单向链表

实现代码如下,这边我使用了迭代器模式,方便节点的单向遍历,因为没有使用MS提供的标准的迭代器接口,所以无法使用foreach遍历.

    /// <summary>
/// C#链表实现
/// </summary>
public class LinkedList
{
static void Main(string[] args)
{
//生成对应的Node节点
var nodeFirst = new Node<string>();
var nodeSecond = new Node<string>();
var nodeThird = new Node<string>(); //构造节点内容
nodeFirst.Item = "one";
nodeSecond.Item = "two";
nodeThird.Item = "three"; //链接节点
nodeFirst.NodeItem = nodeSecond;
nodeSecond.NodeItem = nodeThird;
//注:这里nodeThird的NodeItem指向null var nodeEnumerator = nodeFirst.GetEnumerator();
while (nodeEnumerator.MoveNext())
{
Console.Write($"当前节点元素内容:{nodeEnumerator.CurrentItem}");
//这里如果当前节点的下一个节点不为空,则让当前节点变为下一个节点
if (nodeEnumerator.SetNext())
Console.WriteLine($"下一个节点内容:{nodeEnumerator.CurrentNode.Item}");
else
Console.WriteLine($"链表遍历结束,下一个节点内容为null");
}
Console.ReadKey(); } /// <summary>
/// 节点对象,使用迭代器模式,实现链表的遍历
/// </summary>
/// <typeparam name="T"></typeparam>
public class Node<T> : ILinkedListEnumerable<T>
{
public T Item { get; set; } public Node<T> NodeItem { get; set; } public ILinedListEnumerator<T> GetEnumerator()
{
return new NodeEnumerator<T>(this);
}
} private class NodeEnumerator<T> : ILinedListEnumerator<T>
{
public Node<T> CurrentNode { get; set; } public NodeEnumerator(Node<T> node)
{
CurrentNode = node;
} public T CurrentItem => CurrentNode.Item; public bool MoveNext()
{
if (CurrentNode!=null)
return true;
return false;
} public bool SetNext()
{
if (CurrentNode.NodeItem != null)
{
CurrentNode = CurrentNode.NodeItem;
return true;
}
else {
CurrentNode = null;
return false;
} } /// <summary>
/// 当迭代器内部存在非托管资源时,用于释放资源
/// </summary>
public void Dispose()
{
throw new NotImplementedException();
}
} /// <summary>
/// 链表迭代器接口约束
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinkedListEnumerable<T>
{
ILinedListEnumerator<T> GetEnumerator();
} /// <summary>
/// 链表单个迭代器
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinedListEnumerator<T> : IDisposable
{
/// <summary>
/// 当前迭代器元素
/// </summary>
Node<T> CurrentNode { get; } /// <summary>
/// 当前迭代器元素内容
/// </summary>
T CurrentItem { get; } /// <summary>
/// 是否可以进行下一步遍历操作
/// </summary>
/// <returns></returns>
bool MoveNext(); /// <summary>
/// 遍历完当前链表元素,使其指向下一个元素
/// </summary>
bool SetNext();
}
}

4、实战 双向链表

双向链表的应用场景很多,比如Redis的List就是使用双向链表实现的.这种形式的链表更加的灵活.

修改代码如下:

    /// <summary>
/// C#链表实现
/// </summary>
public class LinkedList
{
static void Main(string[] args)
{
//生成对应的Node节点
var nodeFirst = new Node<string>();
var nodeSecond = new Node<string>();
var nodeThird = new Node<string>(); //构造节点内容
nodeFirst.Item = "one";
nodeSecond.Item = "two";
nodeThird.Item = "three"; //链接节点
nodeFirst.NextNode = nodeSecond;
nodeSecond.NextNode = nodeThird;
//注:这里nodeThird的NextNode指向null var nodeEnumerator = nodeFirst.GetEnumerator();
while (nodeEnumerator.MoveNext())
{
//输出当前节点的内容
Console.Write($"当前节点元素内容:{nodeEnumerator.CurrentItem} "); //输出上一个节点的内容
Console.Write($"上一个节点元素内容:{nodeEnumerator.PreviousNode?.Item??"没有上一个节点"} "); //这里如果当前节点的下一个节点不为空,则让当前节点变为下一个节点
if (nodeEnumerator.SetNext())
Console.WriteLine($"下一个节点内容:{nodeEnumerator.CurrentNode.Item}");
else
Console.WriteLine($"链表遍历结束,下一个节点内容为null");
}
Console.ReadKey(); } /// <summary>
/// 节点对象,使用迭代器模式,实现链表的遍历
/// </summary>
/// <typeparam name="T"></typeparam>
public class Node<T> : ILinkedListEnumerable<T>
{
public T Item { get; set; } public Node<T> NextNode { get; set; } public ILinedListEnumerator<T> GetEnumerator()
{
return new NodeEnumerator<T>(this);
}
} private class NodeEnumerator<T> : ILinedListEnumerator<T>
{
public Node<T> PreviousNode { get; set; } public Node<T> CurrentNode { get; set; } public NodeEnumerator(Node<T> node)
{
CurrentNode = node;
} public T CurrentItem => CurrentNode.Item; public bool MoveNext()
{
if (CurrentNode!=null)
return true;
return false;
} public bool SetNext()
{
if (CurrentNode.NextNode != null)
{
PreviousNode = CurrentNode;
CurrentNode = CurrentNode.NextNode;
return true;
}
else {
CurrentNode = null;
return false;
} } /// <summary>
/// 当迭代器内部存在非托管资源时,用于释放资源
/// </summary>
public void Dispose()
{
throw new NotImplementedException();
}
} /// <summary>
/// 链表迭代器接口约束
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinkedListEnumerable<T>
{
ILinedListEnumerator<T> GetEnumerator();
} /// <summary>
/// 链表单个迭代器
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinedListEnumerator<T> : IDisposable
{
/// <summary>
/// 上一个迭代器元素
/// </summary>
Node<T> PreviousNode { get; } /// <summary>
/// 当前迭代器元素
/// </summary>
Node<T> CurrentNode { get; } /// <summary>
/// 当前迭代器元素内容
/// </summary>
T CurrentItem { get; } /// <summary>
/// 是否可以进行下一步遍历操作
/// </summary>
/// <returns></returns>
bool MoveNext(); /// <summary>
/// 遍历完当前链表元素,使其指向下一个元素
/// </summary>
bool SetNext();
}
}

5、通过双向链表实现反向遍历

如果没有实现链表的双向功能,实现反向遍历的功能是不可能,实际上Redis的List是实现了这个功能的,所以这里我也实现下,tip:目前为止,所以的遍历都是先进先出的,类似于队列,所以如果实现了反向遍历,从而该双向链表同时也支持了先进后出的功能,类似于栈,为了分离正向和反向这个遍历过程,所以我实现了两个迭代器,分别为正向迭代器和反向迭代器.

代码如下:

    /// <summary>
/// C#链表实现
/// </summary>
public class LinkedList
{
static void Main(string[] args)
{ //生成对应的Node节点
var nodeFirst = new Node<string>();
var nodeSecond = new Node<string>();
var nodeThird = new Node<string>(); //构造节点内容
nodeFirst.Item = "one";
nodeSecond.Item = "two";
nodeThird.Item = "three"; //链接节点
nodeFirst.NextNode = nodeSecond;
nodeSecond.NextNode = nodeThird;
nodeSecond.PreviousNode = nodeFirst;
nodeThird.PreviousNode = nodeSecond;
//注:这里nodeThird的NextNode指向null var nodeEnumerator = nodeThird.GetNodeReverseEnumerator();
while (nodeEnumerator.MoveNext())
{
//输出当前节点的内容
Console.Write($"当前节点元素内容:{nodeEnumerator.CurrentItem} "); //输出上一个节点的内容
Console.Write($"上一个节点元素内容:{nodeEnumerator.PreviousNode?.Item ?? "没有上一个节点"} "); //这里如果当前节点的下一个节点不为空,则让当前节点变为下一个节点
if (nodeEnumerator.SetNext())
Console.WriteLine($"下一个节点内容:{nodeEnumerator?.CurrentNode.Item}");
else
Console.WriteLine($"链表遍历结束,下一个节点内容为null"); }
Console.ReadKey();
} /// <summary>
/// 节点对象,使用迭代器模式,实现链表的遍历
/// </summary>
/// <typeparam name="T"></typeparam>
public class Node<T> : ILinkedListEnumerable<T>
{ public T Item { get; set; } public Node<T> PreviousNode { get; set; } public Node<T> NextNode { get; set; } /// <summary>
/// 获取正向迭代器
/// </summary>
/// <returns></returns>
public ILinedListEnumerator<T> GetEnumerator()
{
return new NodeEnumerator<T>(this);
} /// <summary>
/// 获取反向迭代器
/// </summary>
/// <returns></returns>
public ILinedListEnumerator<T> GetNodeReverseEnumerator()
{
return new NodeReverseEnumerator<T>(this);
}
} /// <summary>
/// 正向迭代器
/// </summary>
/// <typeparam name="T"></typeparam>
private class NodeEnumerator<T> : ILinedListEnumerator<T>
{
public Node<T> PreviousNode { get; set; } public Node<T> CurrentNode { get; set; } public NodeEnumerator(Node<T> node)
{
CurrentNode = node;
} public T CurrentItem => CurrentNode.Item; public bool MoveNext()
{
if (PreviousNode != null)
return true;
return false;
} public bool SetNext()
{
if (CurrentNode.PreviousNode != null)
{
PreviousNode = CurrentNode;
CurrentNode = CurrentNode.PreviousNode;
return true;
}
else {
CurrentNode = null;
return false;
} } /// <summary>
/// 当迭代器内部存在非托管资源时,用于释放资源
/// </summary>
public void Dispose()
{
throw new NotImplementedException();
}
} /// <summary>
/// 反向迭代器
/// </summary>
private class NodeReverseEnumerator<T>: ILinedListEnumerator<T>
{
public Node<T> PreviousNode { get; set; } public Node<T> CurrentNode { get; set; } public NodeReverseEnumerator(Node<T> node)
{
CurrentNode = node;
} public T CurrentItem => CurrentNode.Item; public bool MoveNext()
{
if (CurrentNode != null)
return true;
return false;
} public bool SetNext()
{
if (CurrentNode.PreviousNode != null)
{
PreviousNode = CurrentNode;
CurrentNode = CurrentNode.PreviousNode;
return true;
}
else
{
CurrentNode = null;
return false;
} } /// <summary>
/// 当迭代器内部存在非托管资源时,用于释放资源
/// </summary>
public void Dispose()
{
throw new NotImplementedException();
}
} /// <summary>
/// 链表迭代器接口约束
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinkedListEnumerable<T>
{
/// <summary>
/// 正向迭代器
/// </summary>
/// <returns></returns>
ILinedListEnumerator<T> GetEnumerator(); /// <summary>
/// 反向迭代器
/// </summary>
/// <returns></returns>
ILinedListEnumerator<T> GetNodeReverseEnumerator();
} /// <summary>
/// 链表单个迭代器
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinedListEnumerator<T> : IDisposable
{
/// <summary>
/// 上一个迭代器元素
/// </summary>
Node<T> PreviousNode { get; } /// <summary>
/// 当前迭代器元素
/// </summary>
Node<T> CurrentNode { get; } /// <summary>
/// 当前迭代器元素内容
/// </summary>
T CurrentItem { get; } /// <summary>
/// 是否可以进行下一步遍历操作
/// </summary>
/// <returns></returns>
bool MoveNext(); /// <summary>
/// 遍历完当前链表元素,使其指向下一个元素
/// </summary>
bool SetNext();
}
}

C# 算法之链表、双向链表以及正向反向遍历实现的更多相关文章

  1. <正向/反向>最大匹配算法(Java)

    算法描述(正向): 给定最大词长n,待分词文本str,指针f=0,词典dic文档 1 取子串sub=str(f,f+n) 2 如果(遍历dic,有匹配sub) f++; 3 否则 n--; 4 注意: ...

  2. Java数据结构和算法(四)--链表

    日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...

  3. 【05】Nginx:TCP / 正向 / 反向代理 / 负载均衡

    写在前面的话 在我们日常的工作中,不可能所有的服务都是简单的 HTML 静态网页,nginx 作为轻量级的 WEB 服务器,其实我们将它用于更多的地方还是作为我们网站的入口.不管你是后端接口,还是前端 ...

  4. 「C语言」单链表/双向链表的建立/遍历/插入/删除

    最近临近期末的C语言课程设计比平时练习作业一下难了不止一个档次,第一次接触到了C语言的框架开发,了解了View(界面层).Service(业务逻辑层).Persistence(持久化层)的分离和耦合, ...

  5. 图解堆算法、链表、栈与队列(Mark)

    原文地址: 图解堆算法.链表.栈与队列(多图预警) 堆(heap),是一类特殊的数据结构的统称.它通常被看作一棵树的数组对象.在队列中,调度程序反复提取队列中的第一个作业并运行,因为实际情况中某些时间 ...

  6. 算法基础~链表~排序链表的合并(k条)

    算法基础~链表~排序链表的合并(k条) 1,题意:已知k个已排序链表头结点指针,将这k个链表合并,合并后仍然为有序的,返回合并后的头结点. 2,方法之间时间复杂度的比较: 方法1(借助工具vector ...

  7. 打败算法 —— 删除链表的倒数第n个结点

    本文参考 出自LeetCode上的题库 -- 删除链表的倒数第n个结点,官方的双指针解法没有完全符合"只遍历一遍链表"的要求,本文给出另一种双指针解法 https://leetco ...

  8. 算法:图(Graph)的遍历、最小生成树和拓扑排序

    背景 不同的数据结构有不同的用途,像:数组.链表.队列.栈多数是用来做为基本的工具使用,二叉树多用来作为已排序元素列表的存储,B 树用在存储中,本文介绍的 Graph 多数是为了解决现实问题(说到底, ...

  9. python反向遍历一个可迭代对象

    我们通常情况下都是正向遍历一个列表,下面是一种简单的反向遍历一个列表的方式. ## 正向遍历 >>>A = [9, 8, 7] >>>for index, a in ...

随机推荐

  1. java项目测试或者不使用request,如何获取webroot路径

    1.使用jdk中的方法,然后根据项目编译后的文件存在的位置,获取到classes目录,然后向上级查询获取String path = EngineTest.class.getResource(" ...

  2. URL重写中的中文参数问题

    在做搜索功能时,需要输入关键字,如果搜索出来的结果很多,又需要分页.这里用URL重写技术(即href="?keyword=关键字&page=分页数"),就涉及到了传递中文关 ...

  3. JVM思考-ClassLoader.loadClasshe和Class.forName区别

    JVM思考-ClassLoader.loadClasshe和Class.forName区别 目录:JVM总括:目录 见博客第四节:JVM总括四-类加载过程.双亲委派模型.对象实例化过程

  4. MFC里面解析json文件格式

    CString strTemp; //CString ->string; string stringMsg = (LPCSTR)(CStringA)strTemp; //string -> ...

  5. 解决 “access violation at address xxxxxxxxx”错误

    在进行磁盘整理的时候,打开Foxmail的时候出现了“access violation at address32383137”错误 和“access violation at address00000 ...

  6. # 2019-2020-3 《Java 程序设计》第二周学习总结

    2019-2020-3 <Java 程序设计>第二周学习总结 1.通过第二周的学习,利用教材和老师在蓝墨云上的一些教学视频以及通过老师和同学的博客以及一些课外资料,充分学习了第二.三章的内 ...

  7. Django的学习进阶(一)—— 外键的使用

    一.描述 在利用django做网络开发的时候我们会遇到一个问题就是,我们建立了多张数据表,但是多张数据表中的内容是不一样的,但是之间有着联系比如: 我有两张表,一张是记录歌曲信息的内容,一张是对歌曲操 ...

  8. mysql的部署

    mysql在linux系统中的部署: 二进制包安装软件: 第一步:下载二进制软件,上传到服务器 www.mysql.com mkdir /server/tools -y cd /server/tool ...

  9. mongoDB实现MapReduce

    一.MongoDB Map Reduce Map-Reduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE).MongoDB提供的Ma ...

  10. 第35章:MongoDB-集群--Master Slave(主从复制)

    ①主从复制 最基本的设置方式就是建立一个主节点和一个或多个从节点,每个从节点要知道主节点的地址.采用双机备份后主节点挂掉了后从节点可以接替主机继续服务,所以这种模式比单节点的高可用性要好很多. ②注意 ...