C#學習單向鏈表和接口 IList


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;
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM