C#学习单向链表和接口 IList<T>

作者:乌龙哈里
时间:2015-11-04
平台:Window7 64bit,Visual Studio Community 2015

参考:

章节:

  • 单向链表元素
  • 定义单向链表操作接口
  • 逐步实现单向链表

正文:

前面学习了 IEnumerable<T>、IComparable<T>、ICollection<T> 三个接口,就是为了学习数据结构编程使用。

一、单向链表元素

根据单向链表(Single Linked List)的定义,我们知道链表中的元素 Node 由 数据 和 下一个元素 NextNode 两部分组成。构建如下:

//=====单向链表元素=====
class Node<T>
{
    public T Value;
    public Node<T> NextNode;
}

该元素的创立应该有不带参数()和带参数(T value) 两种形式,我们继续补全代码如下:

//=====单向链表元素=====
class Node<T>
{
    public T Value;
    public Node<T> NextNode;

public Node():this(default(T)){ }
    public Node(T value)
    {
        Value = value;
        NextNode = null;
    }
}

不带参数的创立里面有 default(T),参见 MSDN 泛型代码中的默认关键字(C# 编程指南)

二、定义单向链表操作接口

有了元素 Node<T>后,我们要操作它,设想中的操作有 增加、删除、修改、清空、遍历、索引器等,我们看看接口 IColloction<T> 和 IList<T> 的约定,就知道我们要实现 IList<T>。

//======参考 IList<T>===
public interface IList<T> : ICollection<T>
{
    T this[int index] {get;set;}
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);
}

//======参考 ICollection<T>===
public interface ICollection<T> : IEnumerable<T>
{
     int Count { get; }
     bool IsReadOnly { get; }
     void Add(T item);
     void Clear();
     bool Contains(T item);
     void CopyTo(T[] array, int arrayIndex);
     bool Remove(T item);
}

三、逐步实现单向链表

定义一个单向链表类:

//=====单向链表=====
public class SinglyLinkedList<T> : IList<T>
{
}

接下来在VS2015 IDE 的小黄灯泡提示内选实现,让 IDE 帮我们出填空题吧。好长一大串^_^!,共14个方法或属性需要填(以我小菜鸟的经历来看,绝对是我填过最长的一个类)。下来我们只好逐步来实现这个类。

1、加入头元素。先要引入 元素类 Node<T>,把它命名为 head(书本上是这样起名的,本来我想起名叫 root的),按 C# 6.0 的新写法,我们可以直接把它缺省值 =null。刚开始创建链表里面是空的,所以头元素为 null。

//=====单向链表=====
public class SinglyLinkedList<T> : IList<T>
{
    private Node<T> head = null;

2、填写 Add(T item) 方法和 int Count{get;} 属性。由于这是个链表,元素只能直线流动,所以要添加元素只能一个一个遍历到链表最尾端,我们这里加一个计数字段 int _count 。

//=====单向链表=====
public class SinglyLinkedList<T> : IList<T>
{
    private Node<T> head = null;
    private int _count = 0;
    //---添加元素并计数
    public void Add(T item)
    {
        if (head == null)
        {
            head = new Node<T>(item);
            _count++;
        }
        else
        {
            Node<T> node = head;
            while (node.NextNode != null)
            {
                node = node.NextNode;
                _count++;
            }
            node.NextNode = new Node<T>(item);
        }
    }
    //---计数---
    public int Count
    {
        get { return _count; }
    }

好了,终于可以添加元素并开始计数了,实验调用一下,看看是否正确:

static void Main(string[] args)
{
    SinglyLinkedList<int> slist = new SinglyLinkedList<int>();
    Console.WriteLine($"count: {slist.Count}");
    slist.Add(1);
    slist.Add(2);
    slist.Add(3);
    slist.Add(4);
    Console.WriteLine($"count: {slist.Count}");

//输出结果:
//count: 0
//count: 4

3、实现枚举器,使得可以遍历。能够 Add(T item)后,虽然能加入元素,但我们无法显示出来里面元素的值是多少,这时就要实现 IEnumerable<T> 接口,让它能够遍历显示里面的元素值。

//---实现可枚举
public IEnumerator<T> GetEnumerator()
{
    Node<T> node = head;
    Node<T> result = new Node<T>();
    while (node != null)
    {
        result = node;
        node = node.NextNode;
        yield return result.Value;
    }
}
//---不用填写,只调用上面的
IEnumerator IEnumerable.GetEnumerator()
{
    throw new NotImplementedException();
}

接下来调用看看:

Console.Write("elements: ");
foreach (var s in slist) { Console.Write($"{s} "); }

//输出:
//elements: 1 2 3 4

4、实现清空。只要把 head 设置成null ,并把 计数器归零即可。这里我这个小菜鸟不禁想到没有垃圾收集器的语言,那剩下的元素所占内存怎么释放,难道要先遍历逐个释放?或者数据不放在堆上?算了,我水平太次,不思考超出能力范围的事。

//---清空---
public void Clear()
{
    head = null;
    _count = 0;
}

5、实现规定的 Insert(int index,T item)。意思是在 index 位置上插入一个元素。由于是链表,插入一个新值会破坏掉整个链的连接,我们需要在插入新值前保护现场,即把上个元素和当前元素保存起来,然后把在前个元素的 NextNode 里填写新元素,在新元素的 NextNode 里填写当前元素,这样整个链表又完整了。

//---插入值---
public void Insert(int index, T item)
{
    if (index >= 0 && index < _count)
    {
        Node<T> node = head;
        Node<T> prev = null;
        Node<T> next = null;
        //头元素永远是head,所以要专门弄个index=0的特殊
        if (index == 0)
        {
            next = head;
            head = new Node<T>(item);
            head.NextNode = next;
        }
        else
        {
            for (int i = 0; i < index; i++)
            {
                prev = node;
                node = node.NextNode;
            }
            next = node;
            node = new Node<T>(item);
            node.NextNode = next;
            prev.NextNode = node;
        }
        _count++;
    }
    else throw new Exception("Out of Range !");
}

调用检查正确否:

SinglyLinkedList<int> slist = new SinglyLinkedList<int>();
Console.WriteLine($"count: {slist.Count}");

slist.Add(1);
slist.Add(2);
slist.Add(3);
slist.Add(4);

Console.WriteLine($"count: {slist.Count}");
Console.Write("elements: ");
foreach (var s in slist) { Console.Write($"{s} "); }
Console.WriteLine();

slist.Insert(0, 5);
Console.Write("elements: ");
foreach (var s in slist) { Console.Write($"{s} "); }
Console.WriteLine();

Console.WriteLine($"count: {slist.Count}");
Console.ReadKey();

//输出:
count: 0
count: 4
elements: 1 2 3 4
elements: 5 1 2 3 4
count: 5

如果连 index=0 都能插入值,那我们之前的 Add(T item) 可以视作是 Insert(0,T item),但是在 Insert 里面有个index 越界的检查,当链表为空的时候, Count=0,会抛出错误。针对这个,我们在 Add(T item) 前先把 Count +1,Insert 后再 Count -1 回去。Add 改造如下:

//---添加元素并计数
public void Add(T item)
{
    _count++;
    Insert(_count - 1, item);
    _count--;
}

6、填写 int IndexOf(T item) 方法。后面的方法有 int IndexOf(T item)、bool Remove(T item)、void RemoveAt(int index) 、bool Contains(T item) 和一个索引器,这些都和查找元素有关,而 int IndexOf(T item) 无疑就是一个查找元素位置的方法,所以我们先要实现它。
还有一个问题就是,要查找 T item ,T 必须是可比较类型,所以我们在 SinglyLinkedList 类上加个 T 的约束是符合 IComparable<T> 接口的。

//=====单向链表=====
public class SinglyLinkedList<T> : IList<T> where T : IComparable<T>

//---查找---
public int IndexOf(T item)
{
    int result = -1;
    Node<T> node = head;
    for(int i = 0; i < _count; i++)
    {
        if (node.Value.Equals(item))
        {
            result = i;
            break;
        }
        node = node.NextNode;
    }
    return result;
}

有了 int IndexOf(T item) 这个利器,下来的一些方法填空就简单了。

//---包含---
public bool Contains(T item)
{
    return IndexOf(item) > -1 ? true : false;
}
//---删除---
public void RemoveAt(int index)
{
    if (index >= 0 && index < _count)
    {
        Node<T> prev = null;
        Node<T> node = head;
        if (index == 0)
        {
            head = head.NextNode;
        }
        else
        {
            for (int i = 0; i < index; i++)
            {
                prev = node;
                node = node.NextNode;
            }
            prev.NextNode = node.NextNode;
        }
        _count--;
    }
    else throw new Exception("Out of Range !");
}
//---删除---
public bool Remove(T item)
{
    int n = IndexOf(item);
    if (n < 0) { return false; }
    RemoveAt(n);
    return true;
}

7、完成索引器。索引器见参考。下来具体实现:

//---索引器
public T this[int index]
{
    get
    {
        if (index >= 0 && index < _count)
        {
            Node<T> node = head;
            for(int i = 0; i < index;i++)
            {
                node = node.NextNode;
            }
            return node.Value;
        }
        else throw new Exception("Out of Range !");
    }
    set
    {
        Insert(index,value);
    }
}

8、实现拷贝功能:

//---拷贝
public void CopyTo(T[] array, int arrayIndex)
{
    Node<T> node = head;
    for(int i = 0; i < _count; i++)
    {
        array[arrayIndex + i] = node.Value;
        node = node.NextNode;
    }
}

至此,整个单向链表和 IList<T> 接口就学习好了。附完成源程序:

//=====单向链表=====
public class SinglyLinkedList<T> : IList<T> where T : IComparable<T>
{
    private Node<T> head = null;
    private int _count = 0;
    //---添加元素并计数---
    public void Add(T item)
    {
        _count++;
        Insert(_count - 1, item);
        _count--;
    }
    //---计数---
    public int Count
    {
        get { return _count; }
    }
    //---实现可枚举
    public IEnumerator<T> GetEnumerator()
    {
        Node<T> node = head;
        Node<T> result = new Node<T>();
        while (node != null)
        {
            result = node;
            node = node.NextNode;
            yield return result.Value;
        }
    }
    //---不用填写,只调用上面的
    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
    //---清空---
    public void Clear()
    {
        head = null;
        _count = 0;
    }
    //---插入值---
    public void Insert(int index, T item)
    {
        if (index >= 0 && index < _count)
        {
            Node<T> node = head;
            Node<T> prev = null;
            Node<T> next = null;
            //头元素永远是head,所以要专门弄个index=0的特殊
            if (index == 0)
            {
                next = head;
                head = new Node<T>(item);
                head.NextNode = next;
            }
            else
            {
                for (int i = 0; i < index; i++)
                {
                    prev = node;
                    node = node.NextNode;
                }
                next = node;
                node = new Node<T>(item);
                node.NextNode = next;
                prev.NextNode = node;
            }
            _count++;
        }
        else throw new Exception("Out of Range !");
    }
    //---查找---
    public int IndexOf(T item)
    {
        int result = -1;
        Node<T> node = head;
        for (int i = 0; i < _count; i++)
        {
            if (node.Value.Equals(item))
            {
                result = i;
                break;
            }
            node = node.NextNode;
        }
        return result;
    }
    //---包含---
    public bool Contains(T item)
    {
        return IndexOf(item) > -1 ? true : false;
    }
    //---删除---
    public void RemoveAt(int index)
    {
        if (index >= 0 && index < _count)
        {
            Node<T> prev = null;
            Node<T> node = head;
            if (index == 0)
            {
                head = head.NextNode;
            }
            else
            {
                for (int i = 0; i < index; i++)
                {
                    prev = node;
                    node = node.NextNode;
                }
                prev.NextNode = node.NextNode;
            }
            _count--;
        }
        else throw new Exception("Out of Range !");
    }
    //---删除---
    public bool Remove(T item)
    {
        int n = IndexOf(item);
        if (n < 0) { return false; }
        RemoveAt(n);
        return true;
    }
    //---索引器---
    public T this[int index]
    {
        get
        {
            if (index >= 0 && index < _count)
            {
                Node<T> node = head;
                for (int i = 0; i < index; i++)
                {
                    node = node.NextNode;
                }
                return node.Value;
            }
            else throw new Exception("Out of Range !");
        }
        set
        {
            Insert(index, value);
        }
    }
    //---只读?---
    public bool IsReadOnly
    {
        get { return false; }
    }
    //---拷贝---
    public void CopyTo(T[] array, int arrayIndex)
    {
        Node<T> node = head;
        for (int i = 0; i < _count; i++)
        {
            array[arrayIndex + i] = node.Value;
            node = node.NextNode;
        }
    }
}
//=====单向链表元素=====
class Node<T>
{
    public T Value;
    public Node<T> NextNode;
    public Node() : this(default(T)) { }
    public Node(T value)
    {
        Value = value;
        NextNode = null;
    }
}

C#学习单向链表和接口 IList<T>的更多相关文章

  1. Java 单向链表学习

    Java 单向链表学习 链表等同于动态的数组:可以不同设定固定的空间,根据需要的内容动态的改变链表的占用空间和动态的数组同一形式:链表的使用可以更加便于操作. 链表的基本结构包括:链表工具类和节点类, ...

  2. PHP算法学习(6) 单向链表 实现栈

    svn地址:svn://gitee.com/zxadmin/live_z 这个是模拟栈的先进后出的一个链表操作,自动维护链表,当然你也使用SPL的栈 测试版本php 5.4 ,5.6,7.0,7.2 ...

  3. 玩转C线性表和单向链表之Linux双向链表优化

    前言: 这次介绍基本数据结构的线性表和链表,并用C语言进行编写:建议最开始学数据结构时,用C语言:像栈和队列都可以用这两种数据结构来实现. 一.线性表基本介绍 1 概念: 线性表也就是关系户中最简单的 ...

  4. 输出单向链表中倒数第k个结点

    描述 输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第0个结点为链表的尾指针. 链表结点定义如下: struct ListNode { int       m_nKey; ListNode* ...

  5. [置顶] ※数据结构※→☆线性表结构(list)☆============单向链表结构(list single)(二)

    单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始:链表是使用指针进行构造的列表:又称为结点列表,因为链表是由一个个结点组装起来的:其中每个结点都有指 ...

  6. 数据结构-单向链表 C和C++的实现

    数据结构,一堆数据的存放方式. 今天我们学习数据结构中的 链表: 链表的结构: 链表是一种特殊的数组,它的每个元素称为节点,每个节点包括两个部分: 数据域:存放数据,此部分与数组相同 指针域:存放了下 ...

  7. [Java算法分析与设计]--单向链表(List)的实现和应用

    单向链表与顺序表的区别在于单向链表的底层数据结构是节点块,而顺序表的底层数据结构是数组.节点块中除了保存该节点对应的数据之外,还保存这下一个节点的对象地址.这样整个结构就像一条链子,称之为" ...

  8. Alan Cox:单向链表中prev指针的妙用

    之前发过一篇二级指针操作单向链表的例子,显示了C语言指针的灵活性,这次再探讨一个指针操作链表的例子,而且是一种完全不同的用法. 这个例子是linux-1.2.13网络协议栈里的,关于链表遍历& ...

  9. [华为]输出单向链表中倒数第k个结点

    输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第1个结点为链表的尾指针. 链表结点定义如下: struct ListNode { int       m_nKey; ListNode* m_ ...

随机推荐

  1. 遭遇ORA-01078,LRM-00109,ORA-27046 SPFILE文件损坏

    今天在启动数据库时遭遇到 $ sqlplus / as sysdba SQL*Plus: Release 10.2.0.4.0 - Production on Tue Jul 16 21:28:03 ...

  2. Ubuntu 12.04(所有ubuntu发行版都适用)sudo免输入密码

    首先执行以下命令(该命令用来修改 /etc/sudoers 文件): $ sudo gedit /etc/sudoers 然后把  %sudo    ALL=(ALL:ALL) ALL  这行注释掉, ...

  3. UIView的生命周期和layout方法总结

    生命周期 1. loadView 什么时候调用? 每次访问UIViewController的view时候并且view == nil时候调用. 如何实现? 1> 如果在初始化UIViewContr ...

  4. Android 动画的分类

    分为三类: View Animation (补间动画 Tween动画) Drawable Animation(帧动画 Frame动画) Property Animation(android 3.0引入 ...

  5. CSS之纯CSS画的基本图形(矩形、圆形、三角形、多边形、爱心、八卦等)

    图形包括基本的矩形.圆形.椭圆.三角形.多边形,也包括稍微复杂一点的爱心.钻石.阴阳八卦等.当然有一些需要用到CSS3的属性,所以在你打开这篇文章的时候,我希望你用的是firefox或者chrome, ...

  6. 利用fiddler将本地网页放到某个域下

    注: 1)在学习慕课网课程<搜索框制作>中遇到如题困难,查找资料后解决,做此记录.课程网址http://www.imooc.com/video/263. 2)建议同时去学习慕课网课程< ...

  7. C# 线程同步之排它锁/Monitor监视器类

    一.Monitor类说明,提供同步访问对象的机制. 1.位于System.Threading命名空间下,mscorlib.dll程序集中. 2.Monitor通过获取和释放排它锁的方式实现多线程的同步 ...

  8. PSR

    目前包括以下几个规范: PSR-0(弃用) PSR-1 PSR-2 PSR-3 PSR-4 1.PSR-0 自动加载规范,此规范已被启用-本规范已于2014年10月21日被标记为弃用,目前新的替代规范 ...

  9. trove最新命令简单分类解析

    usage: trove [--version] [--debug] [--service-type <service-type>] [--service-name <service ...

  10. windows下配置Java环境变量

    一.首先, JDK的安装路径,在这里我们选择默认安装在C:\Program Files\Java\jdk1.7.0_45\目录下. 二.下面, 设置环境变量: [1]“我的电脑”右键菜单---> ...