C#数据结构与算法之二:线性表

目录

第二章 线性表

2.1 CLR中的线性表

2.2线性表的接口定义

2.3线性表的实现方式

2.3.1顺序表

2.3.2单链表

2.3.3双向链表

2.3.4循环链表

2.4栈和队列

2.4.1栈

2.4.2队列

2.4.3栈和队列应用实例

2.5串和数组

2.5.1串

2.5.2数组


首先感谢siki老师对C#数据结构与算法的讲解。原视频内容戳这里http://www.sikiedu.com

获取更多内容,请访问博主的个人博客 爱吃猫的小鱼干的Blog
 

第二章 线性表

    线性表是最简单、最基本、最常用的数据结构。线性表是线性结构的抽象(Abstract),线性结构的特点是结构中的数据元素之间存在一对一的线性关系。这种一对一的关系指的是数据元素之间的位置关系,即:(1)除第一个位置的数据元素外,其它数据元素位置的前面都只有一个数据元素;(2)除最后一个位置的数据元素外,其它数据元素位置的后面都只有一个元素。也就是说,数据元素是一个接一个的排列。因此,可以把线性表想象为一种数据元素序列的数据结构。线性表就是位置有先后关系,一个接着一个排列的数据结构。

2.1 CLR中的线性表

c# 2.0 提供了泛型的IList<T>接口,实现了List<T>接口的类有List<T>

2.2线性表的接口定义

namespace 线性表
{
    interface IListDS<T>
    {
        int GetLength();
        void Clear();
        bool IsEmpty();
        void Add(T item);
        void Insert(T item, int index);
        T Delete(int index);
        T this[int index] { get; }
        T GetEle(int index);
        int Locate(T value);
        void ToString();
    }
}

2.3线性表的实现方式

2.3.1顺序表

    在计算机内,保存线性表最简单、最自然的方式,就是把表中的元素一个接一个地放进顺序的存储单元,这就是线性表的顺序存储(Sequence Storage)。线性表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素,用这种方式存储的线性表叫顺序表(Sequence List),如图所示。顺序表的特点是表中相邻的数据元素在内存中存储位置也相邻。

图2.3.1

    假设顺序表中的每个数据元素占w个存储单元,设第i个数据元素的存储地址为Loc(ai),则有:Loc(ai)= Loc(a1)+(i-1)*w 1≤i≤n式中的Loc(a1)表示第一个数据元素a1的存储地址,也是顺序表的起始存储地址,称为顺序表的基地址(Base Address)。也就是说,只要知道顺序表的基地址和每个数据元素所占的存储单元的个数就可以求出顺序表中任何一个数据元素的存储地址。并且,由于计算顺序表中每个数据元素存储地址的时间相同,所以顺序表具有任意存取的特点。(可以在任意位置存取东西)

    C#语言中的数组在内存中占用的存储空间就是一组连续的存储区域,因此,数组具有任意存取的特点。所以,数组天生具有表示顺序表的数据存储区域的特性。

namespace 线性表
{
    /// <summary>
    /// 顺序表实现方式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class SeqList<T> : IListDS<T>
    {
        private T[] data;//数据容器
        private int count;//存放数据个数

        public SeqList(int size)//初始化最大容量
        {
            data = new T[size];
            count = 0;
        }

        public SeqList() : this(10)//默认容量为10
        {

        }

        /// <summary>
        /// 通过索引得到数据
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]
        {
            get { return GetEle(index); }
        }

        public void Add(T item)
        {
            if (count == data.Length)
            {
                Console.WriteLine("当前顺序表已存满,不允许再存入数据");
            }
            else
            {
                data[count] = item;
                count++;
            }
        }

        /// <summary>
        /// 清空表
        /// </summary>
        public void Clear()
        {
            count = 0;
        }

        /// <summary>
        /// 按索引删除数据
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T Delete(int index)
        {
            T temp = data[index];
            if (index >= 0 && index < count)
            {
                for (int i = index + 1; i < count; i++)
                {
                    data[i - 1] = data[i];
                }
                count--;
            }
            else
            {
                Console.WriteLine("索引不存在");
            }
            return temp;
        }

        /// <summary>
        /// 取得数据的个数
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T GetEle(int index)
        {
            if (index >= 0 && index <= count - 1)
            {
                return data[index];
            }
            else
            {
                Console.WriteLine("索引不存在");
                return default(T);//返回T的默认值
            }
        }

        /// <summary>
        /// 取得顺序表长度
        /// </summary>
        /// <returns></returns>
        public int GetLength()
        {
            return count;
        }

        /// <summary>
        /// 按索引插入数据
        /// </summary>
        /// <param name="item"></param>
        /// <param name="index"></param>
        public void Insert(T item, int index)
        {
            if (index >= 0 && index < count)
            {
                for (int i = count - 1; i >= index; i--)
                {
                    data[i + 1] = data[i];
                }
                data[index] = item;
                count++;
            }
            else
            {
                Console.WriteLine("索引不存在");
            }
        }

        /// <summary>
        /// 判断表是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            return count == 0;
        }

        /// <summary>
        /// 按值得到索引
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public int Locate(T value)
        {
            for(int i = 0; i < count; i++)
            {
                if (data[i].Equals(value))
                {
                    return i;
                }
            }
            return -1;
        }

        public void ToString()
        {
            if (count == 0)
            {
                Console.WriteLine("无数据存入");
            }
            else
            {
                for (int i = 0; i < count; i++)
                {
                    Console.WriteLine(data[i]);
                }
            }
        }
    }
}
namespace 线性表
{
    class Program
    {
        static void Main(string[] args)
        {
            //使用自己创建的顺序表
            SeqList<string> seqList = new SeqList<string>();
            seqList.Add("Lemon");
            seqList.Add("SS");
            seqList.Add("214");
            seqList.Insert("999", 2);
            seqList.ToString();

            Console.ReadKey();
        }
    }
}

2.3.2单链表

    顺序表是用地址连续的存储单元顺序存储线性表中的各个数据元素,逻辑上相邻的数据元素在物理位置上也相邻。因此,在顺序表中查找任何一个位置上的数据元素非常方便,这是顺序存储的优点。但是,在对顺序表进行插入和删除时,需要通过移动数据元素来实现,影响了运行效率。线性表的另外一种存储结构——链式存储(Linked Storage),这样的线性表叫链表(Linked List)。链表不要求逻辑上相邻的数据元素在物理存储位置上也相邻,因此,在对链表进行插入和删除时不需要移动数据元素,但同时也失去了顺序表可随机存储的优点。

    链表是用一组任意的存储单元来存储线性表中的数据元素(这组存储单元可以是连续的,也可以是不连续的)。那么,怎么表示两个数据元素逻辑上的相邻关系呢?即如何表示数据元素之间的线性关系呢?为此,在存储数据元素时,除了存储数据元素本身的信息外,还要存储与它相邻的数据元素的存储地址信息。这两部分信息组成该数据元素的存储映像(Image),称为结点(Node)。把存储据元素本身信息的域叫结点的数据域(Data Domain),把存储与它相邻的数据元素的存储地址信息的域叫结点的引用域(Reference Domain)。因此,线性表通过每个结点的引用域形成了一根“链条”,这就是“链表”名称的由来。如果结点的引用域只存储该结点直接后继结点的存储地址,则该链表叫单链表(Singly Linked List)。把该引用域叫 next。单链表结点的结构如图所示,图中 data 表示结点的数据域。

图2.3.2

    下图是线性表(a1,a2,a3,a4,a5,a6)对应的链式存储结构示意图。

图2.3.3

    另外一种表示形式:

图2.3.4

namespace 线性表
{
    /// <summary>
    /// 单链表的节点类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class Node<T>
    {
        private T data;//存储数据
        private Node<T> next;//指针,指向下一个节点

        public Node()
        {
            this.data = default(T);
            this.next = null;
        }

        public Node(T value)
        {
            this.data = value;
        }

        public Node(Node<T> next)
        {
            this.next = next;
        }

        public Node(T value,Node<T> next)
        {
            this.data = value;
            this.next = next;
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }

        public Node<T> Next
        {
            get { return next; }
            set { next = value; }
        }
    }
}
namespace 线性表
{
    /// <summary>
    /// 单链表实现方式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class LinkList<T> : IListDS<T>
    {
        private Node<T> headNode;//头结点

        public LinkList()
        {
            headNode = null;
        }

        /// <summary>
        /// 按索引得到数据
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]
        {
            get
            {
                if (index == 0)
                {
                    return headNode.Data;
                }
                else
                {
                    Node<T> temp = headNode;
                    for (int i = 1; i <= index; i++)
                    {
                        temp = temp.Next;
                    }
                    return temp.Data;
                }
            }
        }

        /// <summary>
        /// 添加数据
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)	
        {
            Node<T> newNode = new Node<T>(item);//根据新的数据创建一个新的节点
            if (headNode == null)//头结点为空时这个新节点即为头结点
            {
                headNode = newNode;
            }
            else//访问链表的尾节点
            {
                Node<T> temp = headNode;
                while (true)
                {
                    if (temp.Next != null)
                    {
                        temp = temp.Next;
                    }
                    else
                    {
                        break;
                    }
                }
                temp.Next = newNode;//把新节点放到链表尾部
            }
        }

        /// <summary>
        /// 清空链表
        /// </summary>
        public void Clear()
        {
            headNode = null;
        }

        /// <summary>
        /// 删除数据
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T Delete(int index)
        {
            T data = default(T);
            if (index == 0)
            {
                data = headNode.Data;
                headNode = headNode.Next;
            }
            else
            {
                Node<T> temp = headNode;
                for (int i = 1; i <= index - 1; i++)
                {
                    temp = temp.Next;
                }
                Node<T> preNode = temp;
                Node<T> currentNode = temp.Next;
                data = currentNode.Data;
                Node<T> nextNode = temp.Next.Next;
                preNode.Next = nextNode;
            }
            return data;
        }

        /// <summary>
        /// 按索引的到数据
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T GetEle(int index)
        {
            return this[index];
        }

        public int GetLength()
        {
            if (headNode == null)
            {
                return 0;
            }
            Node<T> temp = headNode;
            int count = 1;
            while (true)
            {
                if (temp.Next != null)
                {
                    count++;
                    temp = temp.Next;
                }
                else
                {
                    break;
                }
            }
            return count;
        }

        /// <summary>
        /// 插入数据
        /// </summary>
        /// <param name="item"></param>
        /// <param name="index"></param>
        public void Insert(T item, int index)
        {
            Node<T> newNode = new Node<T>(item);
            if (index == 0)//插入到头结点
            {
                newNode.Next = headNode;
                headNode = newNode;
            }
            else
            {
                Node<T> temp = headNode;
                for(int i = 1; i <= index - 1; i++)
                {
                    temp = temp.Next;
                }
                Node<T> preNode = temp;
                Node<T> currentNode = temp.Next;
                preNode.Next = newNode;
                newNode.Next = currentNode;
            }
        }

        /// <summary>
        /// 判断链表是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            return headNode == null;
        }

        /// <summary>
        /// 得到数据的索引
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public int Locate(T value)
        {
            Node<T> temp = headNode;
            if (temp == null)
            {
                return -1;
            }
            else
            {
                int index = 0;
                while (true)
                {
                    if (temp.Data.Equals(value))
                    {
                        return index;
                    }
                    else
                    {
                        if (temp.Next != null)
                        {
                            temp = temp.Next;
                            index++;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                return -1;
            }
        }

        public void ToString()
        {
            if (headNode == null)
            {
                Console.WriteLine("无数据存入");
            }
            else
            {
                Node<T> temp = headNode;
                while (true)
                {
                    if (temp != null)
                    {
                        Console.WriteLine(temp.Data);
                        temp = temp.Next;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    }
}

2.3.3双向链表

    前面介绍的单链表允许从一个结点直接访问它的后继结点,所以, 找直接后继结点的时间复杂度是 O(1)。但是,要找某个结点的直接前驱结点,只能从表的头引用开始遍历各结点。如果某个结点的 Next 等于该结点,那么,这个结点就是该结点的直接前驱结点。也就是说,找直接前驱结点的时间复杂度是 O(n), n是单链表的长度。当然,我们也可以在结点的引用域中保存直接前驱结点的地址而不是直接后继结点的地址。这样,找直接前驱结点的时间复杂度只有 O(1),但找直接后继结点的时间复杂度是 O(n)。如果希望找直接前驱结点和直接后继结点的时间复杂度都是 O(1),那么,需要在结点中设两个引用域,一个保存直接前驱结点的地址,叫 prev,一个直接后继结点的地址,叫 next,这样的链表就是双向链表(Doubly Linked List)。双向链表的结点结构示意图如图所示。

图2.3.5

/// <summary>
/// 双向链表节点类
/// </summary>
/// <typeparam name="T"></typeparam>
public class DbNode<T>
{
    private T data; //数据域
    private DbNode<T> prev; //前驱引用域
    private DbNode<T> next; //后继引用域
                            //构造器
    public DbNode(T val, DbNode<T> p)
    {
        data = val;
        next = p;
    }

    //构造器

    public DbNode(DbNode<T> p)
    {
        next = p;
    }

    //构造器
    public DbNode(T val)
    {
        data = val;
        next = null;
    }

    //构造器
    public DbNode()
    {
        data = default(T);
        next = null;
    }

    //数据域属性
    public T Data
    {
        get { return data; }
        set { data = value; }
    }

    //前驱引用域属性
    public DbNode<T> Prev
    {
        get { return prev; }
        set { prev = value; }
    }

    //后继引用域属性
    public DbNode<T> Next
    {
        get { return next; }
        set { next = value; }
    }
}

2.3.4循环链表

    有些应用不需要链表中有明显的头尾结点。在这种情况下,可能需要方便地从最后一个结点访问到第一个结点。此时,最后一个结点的引用域不是空引用,而是保存的第一个结点的地址(如果该链表带结点,则保存的是头结点的地址),也就是头引用的值。带头结点的循环链表(Circular Linked List)如图所示。

 

图2.3.6

2.4栈和队列

    栈和队列是非常重要的两种数据结构,在软件设计中应用很多。栈和队列也是线性结构,线性表、栈和队列这三种数据结构的数据元素以及数据元素间的逻辑关系完全相同,差别是线性表的操作不受限制,而栈和队列的操作受到限制。栈的操作只能在表的一端进行,队列的插入操作在表的一端进行而其它操作在表的另一端进行,所以,把栈和队列称为操作受限的线性表。

2.4.1栈

1.BCL中的栈

    C#2.0 提供了泛型的Stack<T>类,重要的方法如下:

namespace 栈
{
    class Program
    {
        static void Main(string[] args)
        {
            Stack<string> stack = new Stack<string>();
            stack.Push("Lemon");//入栈(添加数据)
            stack.Push("SS");
            stack.Push("13");
            Console.WriteLine(stack.Peek());//取得栈顶的数据,不删除
            Console.WriteLine(stack.Pop());//出栈(删除数据,返回被删除的数据)
            Console.WriteLine(stack.Count);//取得栈中数据的个数
            stack.Clear();//清空所有数据
            Console.ReadKey();
        }
    }
}

2.顺序栈

    用一片连续的存储空间来存储栈中的数据元素(使用数组),这样的栈称为顺序栈(Sequence Stack)。类似于顺序表,用一维数组来存放顺序栈中的数据元素。栈顶指示器 top 设在数组下标为 0 的端, top 随着插入和删除而变化,当栈为空时,top=-1。下图是顺序栈的栈顶指示器 top 与栈中数据元素的关系图。

图2.4.1

namespace 栈
{
    /// <summary>
    /// 栈接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    interface IStackDS<T>
    {
        int Count { get; }
        int GetLength();
        bool IsEmpty();
        void Clear();
        void Push(T item);
        T Pop();
        T Peek();
    }
}
namespace 栈
{
    /// <summary>
    /// 顺序栈
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class SeqStack<T>: IStackDS<T>
    {
        private T[] data;
        private int top;

        public SeqStack(int size)
        {
            data = new T[size];
            top = -1;
        }

        public SeqStack() : this(10)
        {

        }

        //public int Count => throw new NotImplementedException();
        public int Count
        {
            get { return top + 1; } 
        }

        public void Clear()
        {
            top = -1;
        }

        public int GetLength()
        {
            return Count;
        }

        public bool IsEmpty()
        {
            return Count == 0;
        }

        public T Peek()
        {
            return data[top];
        }

        public T Pop()
        {
            T temp = data[top];
            top--;
            return temp;
        }

        public void Push(T item)
        {
            data[top + 1] = item;
            top++;
        }
    }
}

3.链栈

    栈的另外一种存储方式是链式存储,这样的栈称为链栈(Linked Stack)。链栈通常用单链表来表示,它的实现是单链表的简化。所以,链栈结点的结构与单链表结点的结构一样,如图所示。由于链栈的操作只是在一端进行,为了操作方便,把栈顶设在链表的头部,并且不需要头结点。

图2.4.2

    把链栈看作一个泛型类,类名为 LinkStack<T>。 LinkStack<T>类中有一个字段 top 表示栈顶指示器。由于栈只能访问栈顶的数据元素,而链栈的栈顶指示器又不能指示栈的数据元素的个数。所以,求链栈的长度时,必须把栈中的数据元素一个个出栈,每出栈一个数据元素,计数器就增加 1,但这样会破坏栈的结构。为保留栈中的数据元素,需把出栈的数据元素先压入另外一个栈,计算完长度后,再把数据元素压入原来的栈。但这种算法的空间复杂度和时间复杂度都很高,所以,以上两种算法都不是理想的解决方法。理想的解决方法是 LinkStack<T>类增设一个字段 num 表示链栈中结点的个数。

namespace 栈
{
    /// <summary>
    /// 链栈节点
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class Node<T>
    {
        private T data;
        private Node<T> next;

        public Node()
        {
            data = default(T);
            next = null;
        }

        public Node(T data)
        {
            this.data = data;
            next = null;
        }

        public Node(T data,Node<T> next)
        {
            this.data = data;
            this.next = next;
        }

        public Node(Node<T> next)
        {
            data = default(T);
            this.next = next;
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }

        public Node<T> Next
        {
            get { return next; }
            set { next = value; }
        }
    }
}
namespace 栈
{
    /// <summary>
    /// 链栈
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class LinkStack<T> : IStackDS<T>
    {
        private Node<T> top;//栈顶元素节点
        private int count = 0;//栈中元素个数

        //public int Count => throw new NotImplementedException();
        public int Count
        {
            get { return count; }
        }

        public void Clear()
        {
            top = null;
            count = 0;
        }

        public int GetLength()
        {
            return count;
        }

        public bool IsEmpty()
        {
            return count == 0;
        }

        public T Peek()
        {
            return top.Data;
        }

        public T Pop()
        {
            //出栈,取得栈顶元素并删除
            T data = top.Data;
            top = top.Next;
            count--;
            return data;
        }

        public void Push(T item)
        {
            //入栈,把新添加的元素作为栈顶元素(头)节点
            Node<T> newNode = new Node<T>(item);
            newNode.Next = top;
            top = newNode;
            count++;
        }
    }
}

2.4.2队列

    队列(Queue)是插入操作限定在表的尾部而其它操作限定在表的头部进行的线性表。把进行插入操作的表尾称为队尾(Rear),把进行其它操作的头部称为队头(Front)。当队列中没有数据元素时称为空队列(Empty Queue)。

    队列通常记为: Q= (a1,a2,…,an),Q是英文单词queue的第 1 个字母。a1为队头元素,an为队尾元素。这n个元素是按照a1,a2,…,an的次序依次入队的,出对的次序与入队相同,a1第一个出队,an最后一个出队。所以,对列的操作是按照先进先出(First In First Out)或后进后出( Last In Last Out)的原则进行的,因此,队列又称为FIFO表或LILO表。队列Q的操作示意图如图所示。

图2.4.3

    在实际生活中有许多类似于队列的例子。比如,排队取钱,先来的先取,后来的排在队尾。

队列的操作是线性表操作的一个子集。队列的操作主要包括在队尾插入元素、在队头删除元素、取队头元素和判断队列是否为空等。与栈一样,队列的运算是定义在逻辑结构层次上的,而运算的具体实现是建立在物理存储结构层次上的。因此,把队列的操作作为逻辑结构的一部分,每个操作的具体实现只有在确定了队列的存储结构之后才能完成。队列的基本运算不

是它的全部运算,而是一些常用的基本运算。

1.BCL中的队列

C#2.0 提供了泛型Queue<T>类,其方法有:

namespace 队列
{
    class Program
    {
        static void Main(string[] args)
        {
            Queue<int> queue = new Queue<int>();
            //入队
            queue.Enqueue(1);//队首
            queue.Enqueue(2);
            queue.Enqueue(3);//队尾
            Console.WriteLine(queue.Peek());//取得队首的元素,不移除
            Console.WriteLine(queue.Dequeue());//出队,移除队首元素,并返回被移除的元素
            Console.WriteLine(queue.Count);//获取队列中元素的个数
            queue.Clear();//清空元素
            Console.ReadKey();
        }
    }
}

2.(循环)顺序队列
    用一片连续的存储空间来存储队列中的数据元素,这样的队列称为顺序队列(Sequence Queue)。类似于顺序栈,用一维数组来存放顺序队列中的数据元素。队头位置设在数组下标为 0 的端,用 front 表示;队尾位置设在数组的另一端,用 rear 表示。 front 和 rear 随着插入和删除而变化。当队列为空时, front=rear=-1。图是顺序队列的两个指示器与队列中数据元素的关系图。

图2.4.4

 

    如果再有一个数据元素入队就会出现溢出。但事实上队列中并未满,还有空闲空间,把这种现象称为“假溢出”。这是由于队列“队尾入队头出”的操作原则造成的。解决假溢出的方法是将顺序队列看成是首尾相接的循环结构,头尾指示器的关系不变,这种队列叫循环顺序队列(Circular sequence Queue)。循环队列如图所示。

图2.4.5

namespace 队列
{
    /// <summary>
    /// 队列接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    interface IQueueDS<T>
    {
        int Count { get; }//取得队列长度的属性
        int GetLength(); //求队列的长度
        bool IsEmpty(); //判断对列是否为空
        void Clear(); //清空队列
        void Enqueue(T item); //入队
        T Dequeue(); //出队
        T Peek(); //取队头元素

    }
}
namespace 队列
{
    /// <summary>
    /// 循环顺序队列
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class SeqQueue<T> : IQueueDS<T>
    {
        private T[] data;
        private int count;//当前元素个数
        private int front;//队首(队首元素索引-1)
        private int rear;//队尾(队尾元素索引)

        public SeqQueue(int size)
        {
            data = new T[size];
            count = 0;
            front = -1;
            rear = -1;
        }

        public SeqQueue() : this(10) { }

        public int Count
        {
            get { return count; }
        }

        public void Clear()
        {
            count = 0;
            front = rear = -1;
        }

        public T Dequeue()
        {
            if (count > 0)
            {
                T temp = data[front + 1];
                if (front < data.Length - 1)
                {
                    front++;
                }
                else
                {
                    front = -1;
                }
                count--;
                return temp;
            }
            else
            {
                Console.WriteLine("队列为空");
                return default(T);
            }
        }

        public void Enqueue(T item)
        {
            if(count == data.Length)
            {
                Console.WriteLine("队列已满");
            }
            else
            {
                if (rear == data.Length - 1)
                {
                    data[0] = item;
                    rear = 0;
                    count++;
                }
                else
                {
                    data[rear + 1] = item;
                    rear++;
                    count++;
                }
            }
        }

        public int GetLength()
        {
            return Count;
        }

        public bool IsEmpty()
        {
            return count == 0;
        }

        public T Peek()
        {
            T temp = data[front + 1];
            return temp;
        }
    }
}

3.链队列

    队列的另外一种存储方式是链式存储,这样的队列称为链队列(Linked Queue)。同链栈一样,链队列通常用单链表来表示,它的实现是单链表的简化。所以,链队列的结点的结构与单链表一样,如图所示。由于链队列的操作只是在一端进行,为了操作方便,把队头设在链表的头部,并且不需要头结点。

图2.4.6

namespace 队列
{
    /// <summary>
    /// 链队列节点
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class Node<T>
    {
        private T data; //数据域
        private Node<T> next; //引用域
        //构造器
        public Node(T val, Node<T> p)
        {
            data = val;
            next = p;
        }
        //构造器
        public Node(Node<T> p)
        {
            next = p;
        }
        //构造器
        public Node(T val)
        {
            data = val;
            next = null;
        }
        //构造器
        public Node()
        {
            data = default(T);
            next = null;
        }
        //数据域属性
        public T Data
        {
            get
            {
                return data;
            }
            set
            {
                data = value;
            }
        }
        //引用域属性
        public Node<T> Next
        {
            get
            {
                return next;
            }
            set
            {
                next = value;
            }
        }
    }
}
namespace 队列
{
    /// <summary>
    /// 链队列
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class LinkQueue<T> : IQueueDS<T>
    {
        private Node<T> front;
        private Node<T> rear;
        private int count;

        public LinkQueue()
        {
            front = null;
            rear = null;
            count = 0;
        }

        public int Count
        {
            get { return count; }
        }

        public void Clear()
        {
            front = null;
            rear = null;
            count = 0;
        }

        public T Dequeue()
        {
            if(count == 0)
            {
                Console.WriteLine("队列为空");
                return default(T);
            }
            else if(count == 1)
            {
                T temp = front.Data;
                front = rear = null;
                count = 0;
                return temp;
            }
            else
            {
                T temp = front.Data;
                front = front.Next;
                count--;
                return temp;
            }
        }

        public void Enqueue(T item)
        {
            Node<T> newNode = new Node<T>(item);
            if(count == 0)
            {
                front = newNode;
                rear = newNode;
                count++;
            }
            else
            {
                rear.Next = newNode;
                rear = newNode;
                count++;
            }
        }

        public int GetLength()
        {
            return Count;
        }

        public bool IsEmpty()
        {
            throw new NotImplementedException();
        }

        public T Peek()
        {
            if (front != null)
            {
                return front.Data;
            }
            else
            {
                return default(T);
            }
        }
    }
}

2.4.3栈和队列应用实例

    编程判断一个字符串是否是回文。回文是指一个字符序列以中间字符为基准两边字符完全相同,如字符序列“ ACBDEDBCA”是回文。

    算法思想:判断一个字符序列是否是回文,就是把第一个字符与最后一个字符相比较,第二个字符与倒数第二个字符比较,依次类推,第 i 个字符与第 n-i个字符比较。如果每次比较都相等,则为回文,如果某次比较不相等,就不是回文。因此,可以把字符序列分别入队列和栈,然后逐个出队列和出栈并比较出队列的字符和出栈的字符是否相等,若全部相等则该字符序列就是回文,否则就不是回文。

namespace 栈和队列应用
{
    class Program
    {
        static void Main(string[] args)
        {
            Stack<char> stack = new Stack<char>();
            Queue<char> queue = new Queue<char>();
            string str = Console.ReadLine();
            bool isHuiWen = true;

            for(int i = 0; i < str.Length; i++)
            {
                stack.Push(str[i]);
                queue.Enqueue(str[i]);
            }
            while (stack.Count > 0)
            {
                if (stack.Pop() != queue.Dequeue())
                {
                    isHuiWen = false;
                    break;
                }
            }
            Console.WriteLine(isHuiWen);
            Console.ReadKey();
        }
    }
}

2.5串和数组

2.5.1串

1.串的概念

    在应用程序中使用最频繁的类型是字符串。字符串简称串,是一种特殊的线性表,其特殊性在于串中的数据元素是一个个的字符。字符串在计算机的许多方面应用很广。如在汇编和高级语言的编译程序中,源程序和目标程序都是字符串数据。在事务处理程序中,顾客的信息如姓名、地址等及货物的名称、产地和规格等,都被作为字符串来处理。另外,字符串还具有自身的一些特性。因此,把字符串作为一种数据结构来研究。

    串(String)由 n(n≥0)字符组成的有限序列。一般记为:

    S=”c1c2…cn” (n≥0)

    其中, S是串名,双引号作为串的定界符,用双引号引起来的字符序列是串值。 ci( 1≤i≤n)可以是字母、数字或其它字符, n为串的长度,当n=0 时,称为空串(Empty String)。

串中任意个连续的字符组成的子序列称为该串的子串(Substring)。包含子串的串相应地称为主串。子串的第一个字符在主串中的位置叫子串的位置。如串s1”abcdefg”,它的长度是 7,串s2”cdef”的长度是 4, s2是s1的子串, s2的位置是 3。

    如果两个串的长度相等并且对应位置的字符都相等,则称这两个串相等。而在 C#中,比较两个串是否相等还要看串的语言文化等信息。

2.c#中的串

    在 C#中,一个 String 表示一个恒定不变的字符序列集合。 String 类型是封闭类型,所以,它不能被其它类继承,而它直接继承自 object。因此, String 是引用类型,不是值类型,在托管堆上而不是在线程的堆栈上分配空间。String类型还继承了 IComparable 、 ICloneable 、 IConvertible 、 IComparable<string> 、IEnumerable<char>、 IEnumerable 和 IEquatable<string>等接口。 String 的恒定性指的是一个串一旦被创建,就不能将其变长、变短或者改变其中任何的字符。所以,当我们对一个串进行操作时,不能改变字符串,如在下面例子中定义的 StringDS 类中,串连接、串插入和串删除等操作的结果都是生成了新串而没有改变原串。 C#也提供了 StringBuilder 类型来支持高效地动态创建字符串。

    在 C#中,创建串不能用 new 操作符,而是使用一种称为字符串驻留的机制。这是因为 C#语言将 String 看作是基元类型。基元类型是被编译器直接支持的类型,可以在源代码中用文本常量(Literal)来直接表达字符串。当 C#编译器对源代码进行编译时,将文本常量字符串存放在托管模块的元数据中。而当 CLR 初始化时, CLR 创建一个空的散列表,其中的键是字符串,值为指向托管堆中字符串对象的引用。散列表就是哈希表。当 JIT编译器编译方法时,它会在散列表中查找每一个文本常量字符串。如果找不到,就会在托管堆中构造一个新的 String 对象(指向字符串),然后将该字符串和指向该字符串对象的引用添加到散列表中;如果找到了,不会执行任何操作。

3.定义一个串

    由于串中的字符都是连续存储的,而在 C#中串具有恒定不变的特性,即字符串一经创建,就不能将其变长、变短或者改变其中任何的字符。所以,这里不讨论串的链式存储,也不用接口来表示串的操作。同样,把串看作是一个类,类名为 StringDS。取名为 StringDS 是为了和 C#自身的字符串类 String 相区别。类StringDS 只有一个字段,即存放串中字符序列的数组 data。由于串的运算有很多,类 StringDS 中只包含部分基本的运算。

namespace 串
{
    /// <summary>
    /// 串的实现
    /// </summary>
    class StringDS
    {
        private char[] data;

        //构造器
        public StringDS(char[] array)
        {
            data = new char[array.Length];
            for(int i = 0; i < data.Length; i++)
            {
                data[i] = array[i];
            }
        }

        public StringDS(string str)
        {
            data = new char[str.Length];
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = str[i];
            }
        }

        public StringDS() { data = null; }

        //根据索引取字符的索引器
         public char this[int index]
        {
            get { return data[index]; }
        }

        //获取字符串长度
        public int GetLength()
        {
            return data.Length;
        }

        /// <summary>
        /// 当前字符串等于s,返回0
        /// 当前字符串小于s,返回-1
        /// 当前字符串大于s,返回1
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public int Compare(StringDS s)
        {
            int len = this.GetLength() < s.GetLength() ? this.GetLength() : s.GetLength();//取得长度较小的字符串长度
            int index = -1;//存储字符不相等位置的索引
            for(int i = 0; i < len; i++)
            {
                if (this[i] != s[i])
                {
                    index = i;
                    break;
                }
            }
            if (index != -1)
            {
                if (this[index] > s[index])
                {
                    return 1;
                }
                else
                {
                    return -1;
                }
            }
            else
            {
                if (this.GetLength() == s.GetLength())
                {
                    return 0;
                }
                else
                {
                    if (this.GetLength() < s.GetLength())
                    {
                        return -1;
                    }
                    else
                    {
                        return 1;
                    }
                }
            }
        }

        //截取串
        public StringDS SubString(int index,int length)
        {
            char[] newData;
            if (index < 0 || length <= 0 || index > this.GetLength() - 1)
            {
                Console.WriteLine("警告,可能越界");
                return default(StringDS);
            }
            else
            {
                if (index + length <= this.GetLength())
                {
                    newData = new char[length];
                    for (int i = index; i < index + length; i++)
                    {
                        newData[i - index] = data[i];
                    }
                    return new StringDS(newData);
                }
                else
                {
                    newData = new char[this.GetLength() - index];
                    for(int i = index; i < this.GetLength(); i++)
                    {
                        newData[i - index] = data[i];
                    }
                    return new StringDS(newData);
                }
            }
        }

        //连接串
        public static StringDS Concal(StringDS s1,StringDS s2)
        {
            char[] newData = new char[s1.GetLength() + s2.GetLength()];
            for(int i = 0; i < s1.GetLength(); i++)
            {
                newData[i] = s1[i];
            }
            for(int i = s1.GetLength(); i < newData.Length; i++)
            {
                newData[i] = s2[i - s1.GetLength()];
            }
            return new StringDS(newData);
        }

        /// <summary>
        /// 查询串
        /// 返回当前串中首个与s相同的子串的首索引
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public int IndexOf(StringDS s)
        {
            if (this.GetLength() < s.GetLength())
            {
                Console.WriteLine("当前串长度小于目标串");
                return -1;
            }
            else
            {
                for(int i = 0; i <= this.GetLength() - s.GetLength(); i++)
                {
                    bool isEqual = true;
                    for (int j = i; j < i + s.GetLength(); j++)
                    {
                        if (this[j] != s[j - i])
                        {
                            isEqual = false;
                        }
                    }
                    if (isEqual)
                    {
                        return i;
                    }
                    else
                    {
                        continue;
                    }
                }
                return -1;
            }
        }

        public void OutPut()
        {
            for(int i = 0; i < data.Length; i++)
            {
                Console.Write(data[i]);
            }
            Console.WriteLine();
        }

        //public string ToString()
        //{
        //    return new string(data);
        //}
    }
}
namespace 串
{
    class Program
    {
        static void Main(string[] args)
        {
            StringDS strDS1 = new StringDS("Lemon de ");
            StringDS strDS2 = new StringDS("SS");
            Console.WriteLine(strDS1.GetLength());
            strDS1.SubString(2, 3).OutPut();
            StringDS.Concal(strDS1, strDS2).OutPut();//不创建类的实例对象,访问类的静态方法
            Console.WriteLine(strDS1.IndexOf(strDS1));
            Console.ReadKey();
        }
    }
}

2.5.2数组

    数组是一种常用的数据结构,可以看作是线性表的推广。数组作为一种数据结构,其特点是结构中的数据元素可以是具有某种结构的数据,甚至可以是数组,但属于同一数据类型。数组在许多高级语言里面都被作为固定类型来使用。

    数组是 n(n≥1)个相同数据类型的数据元素的有限序列。一维数组可以看作是一个线性表,二维数组可以看作是“数据元素是一维数组”的一维数组,三维数组可以看作是“数据元素是二维数组”的一维数组,依次类推。

    C#支持一维数组、多维数组及交错数组(数组的数组)。所有的数组类型都隐含继承自System Array。Array 是一个抽象类,本身又继承自 System.Object。所以,数组总是在托管堆上分配空间,是引用类型。任何数组变量包含的是一个指向数组的引用,而非数组本身。当数组中的元素的值类型时,该类型所需的内存空间也作为数组的一部分而分配;当数组的元素是引用类型时,数组包含是只是引用。

using System;

using System.Collections;

public abstract class Array : ICloneable, IList, ICollection, IEnumerable

{

//判断 Array 是否具有固定大小。

    public bool IsFixedSize { get; }

//获取 Array 元素的个数。

    public int Length { get; }

//获取 Array 的秩(维数)。

    public int Rank { get; }

//实现的 IComparable 接口,在.Array 中搜索特定元素。

    public static int BinarySearch(Array array, object value);

//实现的 IComparable<T>泛型接口,在 Array 中搜索特定元素。

    public static int BinarySearch<T>(T[] array, T value);

//实现 IComparable 接口,在 Array 的某个范围中搜索值。

    public static int BinarySearch(Array array, int index,

        int length, object value);

//实现的 IComparable<T>泛型接口,在 Array 中搜索值。

    public static int BinarySearch<T>(T[] array,

        int index, int length, T value);

//Array 设置为零、 false 或 null,具体取决于元素类型。

    public static void Clear(Array array, int index, int length);

//System.Array 的浅表副本。

    public object Clone();

//从第一个元素开始复制 Array 中的一系列元素

//到另一 Array 中(从第一个元素开始)。

    public static void Copy(Array sourceArray,

        Array destinationArray, int length);

//将一维 Array 的所有元素复制到指定的一维 Array 中。

    public void CopyTo(Array array, int index);

//创建使用从零开始的索引、具有指定 Type 和维长的多维 Array。

    public static Array CreateInstance(Type elementType,

        params int[] lengths);

//返回 ArrayIEnumerator。

    public IEnumerator GetEnumerator();

//获取 Array 指定维中的元素数。

    public int GetLength(int dimension);

//获取一维 Array 中指定位置的值。

    public object GetValue(int index);

//返回整个一维 Array 中第一个匹配项的索引。

    public static int IndexOf(Array array, object value);

//返回整个.Array 中第一个匹配项的索引。

    public static int IndexOf<T>(T[] array, T value);

//返回整个一维 Array 中最后一个匹配项的索引。

    public static int LastIndexOf(Array array, object value);

//反转整个一维 Array 中元素的顺序。

    public static void Reverse(Array array);

//设置给一维 Array 中指定位置的元素。

    public void SetValue(object value, int index);

//对整个一维 Array 中的元素进行排序。

    public static void Sort(Array array);

}

获取更多内容,请访问博主的个人博客 爱吃猫的小鱼干的Blog
 

C#数据结构与算法基础之一:基本概念 上一章    下一章

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C#版本数据结构,用C#的同志们有福啦 本书节选: 第1章 绪论 数据是外部世界信息的计算机化,是计算机加工处理的对象。运用计算机处 理数据时,必须解决四个方面的问题:一是如何在计算机中方便、高效地表示和 组织数据;二是如何在计算机存储器(内存和外存)中存储数据;三是如何对存 储在计算机中的数据进行操作,可以有哪些操作,如何实现这些操作以及如何对 同一问题的不同操作方法进行评价;四是必须理解每种数据结构的性能特征,以 便选择一个适合于某个特定问题的数据结构。这些问题就是数据结构这门课程所 要研究的主要问题。本章首先说明学习数据结构的必要性和本书的目的,然后解 释数据结构及其有关概念,接着讨论算法的相关知识,最后简单介绍本书所要用 到的相关数学知识和C#知识。 1.1 数据结构 1.1.1 学习数据结构的必要性 我们知道,虽然每个人都懂得英语的语法与基本类型,但是对于同样的题目, 每个人写出的作文,水平却高低不一。程序设计也和写英语作文一样,虽然程序 员都懂得语言的语法与语义,但是对于同样的问题,程序员写出来的程序不一样。 有的人写出来的程序效率很高,有的人却用复杂的方法来解决一个简单的问题。 当然,程序设计水平的提高仅仅靠看几本程序设计书是不行的。只有多思索、 多练习,才能提高自己的程序设计水平;否则,书看得再多,提高也不大。记得 刚学程序设计时,常听人说程序设计水平要想提高,最重要的是多看别人写的程 序,多去思考问题。从别人写的程序中,我们可以发现效率更高的解决方法;从 思考问题的过程中,我们可以了解解决问题的方法常常不只一个。运用先前解决 问题的经验,来解决更复杂更深入的问题,是提高程序设计水平的最有效途径。 数据结构正是前人在思索问题的过程中所想出的解决方法。一般而言,在学 习程序设计一段时间后,学习“数据结构”便能让你的程序设计水平上一个台阶。 如果只学会了程序设计的语法和语义,那么你只能解决程序设计三分之一的问 题,而且运用的方法并不是最有效的。但如果学会了数据结构的概念,就能在程 序设计上,运用最有效的方法来解决绝大多数的问题。 《数据结构》这门课程的目的有三个。第一个是讲授常用的数据结构,这些 数据结构形成了程序员基本数据结构工具箱(toolkit)。对于许多常见的问题,工 具箱里的数据结构是理想的选择。就像.NET Framework 中Windows应用程序开 发中的工具箱,程序员可以直接拿来或经过少许的修改就可以使用,非常方便。 第二个是讲授常用的算法,这和数据结构一样,是人们在长期实践过程中的总结, 程序员可以直接拿来或经过少许的修改就可以使用。可以通过算法训练来提高程 序设计水平。第三个目的是通过程序设计的技能训练促进程序员综合能力的提 高。 1.1.2 基本概念和术语 在本小节中,将对一些常用的概念和术语进行介绍,这些概念和术语在以后 的章节中会多次出现。 1、数据(Data) 数据是外部世界信息的载体,它能够被计算机识别、存储和加工处理,是计 算机程序加工的原料。计算机程序处理各种各样的数据,可以是数值数据,如整 数、实数或复数;也可以是非数值数据,如字符、文字、图形、图像、声音等。 2、数据元素(Data Element)和数据项(Data Item) 数据结构(C#语言版) 1.1 数据结构2 数据元素是数据的基本单位,在计算机程序中通常被作为一个整体进行考虑 和处理。数据元素有时也被称为元素、结点、顶点、记录等。一个数据元素可由 若干个数据项(Data Item)组成。数据项是不可分割的、含有独立意义的最小数据 单位,数据项有时也称为字段(Field)或域(Domain)。例如,在数据库信息处理系 统中,数据表中的一条记录就是一个数据元素。这条记录中的学生学号、姓名、 性别、籍贯、出生年月、成绩等字段就是数据项。数据项分为两种,一种叫做初 等项,如学生的性别、籍贯等,在处理时不能再进行分割;另一种叫做组合项, 如学生的成绩,它可以再分为数学、物理、化学等更小的项。 3、数据对象(Data Object) 数据对象是性质相同的数据元素的集合,是数据的一个子集。例如,整数数 据对象是{0,±1,±2,±3,…},字符数据对象是{a,b,c,…}。 4、数据类型(Data Type) 数据类型是高级程序设计语言中的概念,是数据的取值范围和对数据进行操 作的总和。数据类型规定了程序中对象的特性。程序中的每个变量、常量或表达 式的结果都应该属于某种确定的数据类型。例如,C#语言中的字符串类型(String, 经常写为string)。一 个String表示一个恒定不变的字符序列集合,所有的字符序 列集合构成String的取值范围。我们可以对String进行求长度、复制、连接两个 字符串等操作。 数据类型可分为两类:一类是非结构的原子类型,如C#语言中的基本类型 (整型、实型、字符型等);另一类是结构类型,它的成分可以由多个结构类型 组成,并可以分解。结构类型的成分可以是非结构的,也可以是结构的。例如, C#语言中数组的成分可以是整型等基本类型,也可以是数组等结构类型。 5、数据结构(Data Structure) 数据结构是相互之间存在一种或多种特定关系的数据元素的集合。在任何问 题中,数据元素之间都不是孤立的,而是存在着一定的关系,这种关系称为结构 (Structure)。根据数据元素之间关系的不同特性,通常有4类基本数据结构: (1) 集合(Set):如图1.1(a)所示,该结构中的数据元素除了存在“同属于一个集 合”的关系外,不存在任何其它关系。 (2) 线性结构(Linear Structure):如图1.1(b)所示,该结构中的数据元素存在着一 对一的关系。 (3) 树形结构(Tree Structure):如图1.1(c)所示,该结构中的数据元素存在着一对 多的关系。 (4) 图状结构(Graphic Structure):如图1.1(d)所示,该结构中的数据元素存在着 多对多的关系。 (a) 集合 (b) 线性结构 (c) 树形结构 (d)图状结构 图 1.1 4 类基本数据结构关系图 由于集合中的元素的关系极为松散,可用其它数据结构来表示,所以本书不 做专门介绍。关于集合的概念在1.3.1小节中有介绍。 数据结构的形式化定义为: 数据结构(C#语言版) 1.1 数据结构3 数据结构(Data Structure)简记为DS,是一个二元组, DS = (D,R) 其中:D是数据元素的有限集合, R是数据元素之间关系的有限集合。 下面通过例题来进一步理解后3类数据结构。 【例1-1】 学生信息表(如表1.1所示.)是一个线性的数据结构,表中的每 一行是一个记录(在数据库信息处理系统中,表中的一个数据元素称为一个记 录)。一条记录由学号、姓名、行政班级、性别和出生年月等数据项组成。表中 数据元素之间的关系是一对一的关系。 表 1.1 学生信息表 学号 姓名 行政班级 性别 出生年月 040303001 雷洪 软件04103 男 1986.12 040303002 李春 软件04103 女 1987.3 040303003 周刚 软件04103 男 1986.9 【例1-2】 家族关系是典型的树形结构,图1.2是一个三代的家族关系。在 图中,爷爷、儿子、女儿、孙子、孙女或外孙女是一个结点(在树形结构中,数 据元素称为结点),他们之间是一对多的关系。其中,爷爷有两个儿子和一个女 儿,这是一对三的关系;一个儿子有两个儿子(爷爷的孙子),这是一对二的关 系;另一个儿子有一个儿子(爷爷的孙子)和一个女儿(爷爷的孙女),这是一 对二的关系;女儿有三个女儿(爷爷的外孙女),这是一对三的关系。树形结构 具有严格的层次关系,爷爷在树形结构的最上层,中间层是儿子和女儿,最下层 是孙子、孙女和外孙女。不能把这种关系倒过来,因为绝对不会先有儿子或女儿 再有爷爷,也不会先有孙子或孙女再有儿子、先有外孙女再有女儿。 外孙女 爷爷 儿子 儿子 女儿 孙子 孙子 孙子 孙女 外孙女 外孙女 图 1.2 家族关系图 【例1-3】 图1.3是四个城市的公路交通图,这是一个典型的图状结构。在 图中,每个城市是一个顶点(在图状结构中,数据元素称为顶点),它们之间是 多对多的关系。成都与都江堰、雅安直接通公路,都江堰与成都、青城山直接通 公路,青城山与都江堰、成都及雅安直接通公路,雅安与成都、青城山直接通公 路。这些公路构成了一个公路交通网,所以,又把图状结构称为网状结构(Network Structure) 数据结构(C#语言版) 1.2 算法4 成都 都江堰 青城山 雅安 图 1.3 四城市交通图 从数据类型和数据结构的概念可知,二者的关系非常密切。数据类型可以看 作是简单的数据结构。数据的取值范围可以看作是数据元素的有限集合,而对数 据进行操作的集合可以看作是数据元素之间关系的集合。 数据结构包括数据的逻辑结构和物理结构。上述数据结构的定义就是数据的 逻辑结构(Logic Structure),数据的逻辑结构是从具体问题抽象出来的数学模型, 是为了讨论问题的方便,与数据在计算机中的具体存储没有关系。然而,我们讨 论数据结构的目的是为了在计算机中实现对它的操作,因此还需要研究在计算机 中如何表示和存储数据结构,即数据的物理结构(Physical Structure)。数据的物理 结构又称为存储结构(Storage Structure),是数据在计算机中的表示(又叫映像) 和存储,包括数据元素的表示和存储以及数据元素之间关系的表示和存储。 数据的存储结构包括顺序存储结构和链式存储结构两种。顺序存储结构 (Sequence Storage Structure)是通过数据元素在计算机存储器中的相对位置来表 示出数据元素的逻辑关系,一般把逻辑上相邻的数据元素存储在物理位置相邻的 存储单元中。在C#语言中用数组来实现顺序存储结构。因为数组所分配的存储 空间是连续的,所以数组天生就具有实现数据顺序存储结构的能力。链式存储结 构(Linked Storage Structure)对逻辑上相邻的数据元素不要求其存储位置必须相 邻。链式存储结构中的数据元素称为结点(Node),在结点中附设地址域(Address Domain)来存储与该结点相邻的结点的地址来实现结点间的逻辑关系。这个地址 称为引用(Reference),这个地址域称为引用域(Reference Domain)。 从20世纪60年代末到70年代初,出现了大型程序,软件也相对独立,人 们越来越重视数据结构,认为程序设计的实质是确定数据结构,加上设计一个好 的算法,这就是人们常说的“程序=数据结构+算法”。下一节谈谈算法的问题。 1.2 算法 从上节我们知道,算法与数据结构和程序的关系非常密切。进行程序设计时, 先确定相应的数据结构,然后再根据数据结构和问题的需要设计相应的算法。由 于篇幅所限,下面只从算法的特性、算法的评价标准和算法的时间复杂度等三个 方面进行介绍。 1.2.1 算法的特性 算法(Algorithm)是对某一特定类型的问题的求解步骤的一种描述,是指令的 有限序列。其中的每条指令表示一个或多个操作。一个算法应该具备以下5个特 性: 1、有穷性(Finity):一个算法总是在执行有穷步之后结束,即算法的执行时间是 有限的。 2、确定性(Unambiguousness):算法的每一个步骤都必须有确切的含义,即无二 义,并且对于相同的输入只能有相同的输出。 3、输入(Input):一个算法具有零个或多个输入。它即是在算法开始之前给出的 数据结构(C#语言版) 1.2 算法5 量。这些输入是某数据结构中的数据对象。 4、 输出(Output):一个算法具有一个或多个输出,并且这些输出与输入之间存 在着某种特定的关系。 5、 能行性(realizability):算法中的每一步都可以通过已经实现的基本运算的有 限次运行来实现。 算法的含义与程序非常相似,但二者有区别。一个程序不一定满足有穷性。 例如操作系统,只要整个系统不遭破坏,它将永远不会停止。还有,一个程序只 能用计算机语言来描述,也就是说,程序中的指令必须是机器可执行的,而算法 不一定用计算机语言来描述,自然语言、框图、伪代码都可以描述算法。 在本书中我们尽可能采用C#语言来描述和实现算法,使读者能够阅读或上 机执行,以便更好地理解算法。 1.2.2 算法的评价标准 对于一个特定的问题,采用的数据结构不同,其设计的算法一般也不同,即 使在同一种数据结构下,也可以采用不同的算法。那么,对于解决同一问题的不 同算法,选择哪一种算法比较合适,以及如何对现有的算法进行改进,从而设计 出更适合于数据结构的算法,这就是算法评价的问题。评价一个算法优劣的主要 标准如下: 1、正确性(Correctness)。算法的执行结果应当满足预先规定的功能和性能的要求, 这是评价一个算法的最重要也是最基本的标准。算法的正确性还包括对于输入、 输出处理的明确而无歧义的描述。 2、可读性(Readability)。算法主要是为了人阅读和交流,其次才是机器的执行。 所以,一个算法应当思路清晰、层次分明、简单明了、易读易懂。即使算法已转 变成机器可执行的程序,也需要考虑人能较好地阅读理解。同时,一个可读性强 的算法也有助于对算法中隐藏错误的排除和算法的移植。 3、健壮性(Robustness)。一个算法应该具有很强的容错能力,当输入不合法的数 据时,算法应当能做适当的处理,使得不至于引起严重的后果。健壮性要求表明 算法要全面细致地考虑所有可能出现的边界情况和异常情况,并对这些边界情况 和异常情况做出妥善的处理,尽可能使算法没有意外的情况发生。 4、运行时间(Running Time)。运行时间是指算法在计算机上运行所花费的时间, 它等于算法中每条语句执行时间的总和。对于同一个问题如果有多个算法可供选 择,应尽可能选择执行时间短的算法。一般来说,执行时间越短,性能越好。 5、占用空间(Storage Space)。占用空间是指算法在计算机上存储所占用的存储空 间,包括存储算法本身所占用的存储空间、算法的输入及输出数据所占用的存储 空间和算法在运行过程中临时占用的存储空间。算法占用的存储空间是指算法执 行过程中所需要的最大存储空间,对于一个问题如果有多个算法可供选择,应尽 可能选择存储量需求低的算法。实际上,算法的时间效率和空间效率经常是一对 矛盾,相互抵触。我们要根据问题的实际需要进行灵活的处理,有时需要牺牲空 间来换取时间,有时需要牺牲时间来换取空间。 通常把算法在运行过程中临时占用的存储空间的大小叫算法的空间复杂度 (Space Complexity)。算法的空间复杂度比较容易计算,它主要包括局部变量所占 用的存储空间和系统为实现递归所使用的堆栈占用的存储空间。 如果算法是用计算机语言来描述的,还要看程序代码量的大小。对于同一个 问题,在用上面5条标准评价的结果相同的情况下,代码量越少越好。实际上, 代码量越大,占用的存储空间会越多,程序的运行时间也可能越长,出错的可能 数据结构(C#语言版) 1.2 算法6 性也越大,阅读起来也越麻烦。 在以上标准中,本书主要考虑程序的运行时间,也考虑执行程序所占用的空 间。影响程序运行时间的因素很多,包括算法本身、输入的数据以及运行程序的 计算机系统等。计算机的性能由以下因素决定: 1、硬件条件。包括所使用的处理器的类型和速度(比如,使用双核处理器还是 单核处理器)、可使用的内存(缓存和RAM)以及可使用的外存等。 2、实现算法所使用的计算机语言。实现算法的语言级别越高,其执行效率相对 越低。 3、所使用的语言的编译器/解释器。一般而言,编译的执行效率高于解释,但解 释具有更大的灵活性。 4、所使用的操作系统软件。操作系统的功能主要是管理计算机系统的软件和硬 件资源,为计算机用户方便使用计算机提供一个接口。各种语言处理程序如编译 程序、解释程序等和应用程序都在操作系统的控制下运行。 1.2.3 算法的时间复杂度 一个算法的时间复杂度(Time Complexity)是指该算法的运行时间与问题规 模的对应关系。一个算法是由控制结构和原操作构成的,其执行的时间取决于二 者的综合效果。为了便于比较同一问题的不同算法,通常把算法中基本操作重复 执行的次数(频度)作为算法的时间复杂度。算法中的基本操作一般是指算法中 最深层循环内的语句,因此,算法中基本操作语句的频度是问题规模n的某个函 数f(n),记作:T(n)=O(f(n))。其中“O”表示随问题规模n的增大,算法执行时 间的增长率和f(n)的增长率相同,或者说,用“O”符号表示数量级的概念。例 如,如 )1n(n 2 1 )n(T −= ,则 )1n(n 2 1 −的数量级与n2 相同,所以T(n)=O(n2 )。
将数据结构与C#语言和.NET框架结合是本书的一大特点。本书分为8章,第1章介绍了数据结构和算法的基本概念及本书用到的数学和C#的知识;第2章至第6章分别讨论了线性表、栈和队列、串和数组、树型结构和图结构等常用的数据结构及其应用,以及在.NET框架中相应的数据结构;第7、8两章分别讨论了排序和查找常用的各种方法及其应用以及在.NET框架中相应的算法。 第1章绪论...........................................................................................................................1 1.1 数据结构...................................................................................................................1 1.1.1 学习数据结构的必要性...................................................................................1 1.1.2 基本概念和术语...............................................................................................1 1.2 算法...........................................................................................................................4 1.2.1算法的特性............................................................................................................4 1.2.2算法的评价标准....................................................................................................5 1.2.3算法的时间复杂度................................................................................................6 1.3 数学预备知识...........................................................................................................7 1.3.1 集合...................................................................................................................7 1.3.2 常用的数学术语...............................................................................................8 1.3.3 对数...................................................................................................................8 1.3.4 递归...................................................................................................................9 1.4 C#预备知识.............................................................................................................10 1.4.1 接口.................................................................................................................10 1.4.2 泛型编程.........................................................................................................13 本章小结................................................................................................................................20 习题一....................................................................................................................................20 第2章线性表.....................................................................................................................22 2.1 线性表的逻辑结构.........................................................................................................22 2.1.1 线性表的定义.....................................................................................................22 2.1.2 线性表的基本操作.............................................................................................22 2.2 顺序表.............................................................................................................................24 2.2.1 顺序表的定义.....................................................................................................24 2.2.2 顺序表的基本操作实现.....................................................................................29 2.2.3 顺序表应用举例.................................................................................................35 2.3 单链表.............................................................................................................................38 2.3.1 单链表的定义.....................................................................................................39 2.3.2 单链表的基本操作实现.....................................................................................46 2.3.3 单链表应用举例.................................................................................................56 2.4 其他链表.........................................................................................................................61 2.4.1 双向链表.............................................................................................................61 2.4.2循环链表..............................................................................................................64 2.5 C#中的线性表.................................................................................................................64 本章小结................................................................................................................................67 习题二....................................................................................................................................67 第3章栈和队列.................................................................................................................69 3.1 栈....................................................................................................................................69 3.1.1 栈的定义及基本运算.........................................................................................69 3.1.2 栈的存储和运算实现.........................................................................................70 3.1.3 栈的应用举例.....................................................................................................82 3.1.4 C#中的栈.............................................................................................................87 3.2 队列................................................................................................................................87 3.2.1队列的定义及基本运算......................................................................................87 数据结构(C#语言版) 目录 II 3.2.2 队列的存储和运算实现.....................................................................................89 3.2.3 队列的应用举例...............................................................................................103 3.2.4 C# 中的队列.....................................................................................................104 本章小结...............................................................................................................................105 习题三..................................................................................................................................105 第4章串和数组...............................................................................................................106 4.1 串..................................................................................................................................106 4.1.1 串的基本概念...................................................................................................106 4.1.2 串的存储及类定义...........................................................................................106 4.1.3 串的基本操作的实现.......................................................................................111 4.1.4 C#中的串...........................................................................................................115 4.2 数组...............................................................................................................................117 4.2.1 数组的逻辑结构...............................................................................................117 4.2.2 数组的内存映象...............................................................................................118 4.2.3 C#中的数组.......................................................................................................119 本章小结...............................................................................................................................121 习题四..................................................................................................................................121 第5章树和二叉树...........................................................................................................123 5.1 树..................................................................................................................................123 5.1.1 树的定义...........................................................................................................123 5.1.2 树的相关术语...................................................................................................124 5.1.3 树的逻辑表示...................................................................................................125 5.1.4 树的基本操作...................................................................................................126 5.2 二叉树...........................................................................................................................126 5.2.1 二叉树的定义...................................................................................................127 5.2.2 二叉树的性质...................................................................................................128 5.2.3 二叉树的存储结构...........................................................................................129 5.2.4二叉链表存储结构的类实现............................................................................132 5.2.5 二叉树的遍历...................................................................................................137 5.3 树与森林.......................................................................................................................141 5.3.2 树、森林与二叉树的转换...............................................................................144 5.3.3 树和森林的遍历...............................................................................................147 5.4哈夫曼树........................................................................................................................147 5.4.1哈夫曼树的基本概念........................................................................................147 5.4.2哈夫曼树类的实现............................................................................................149 5.4.3哈夫曼编码........................................................................................................153 5.5 应用举例...............................................................................................................154 5.6 C#中的树...............................................................................................................157 本章小结...............................................................................................................................158 习题五..................................................................................................................................159 第6章图...........................................................................................................................161 6.1 图的基本概念................................................................................................................161 6.1.1 图的定义.............................................................................................................161 6.1.2 图的基本术语...................................................................................................161 数据结构(C#语言版) 目录 III 6.1.3 图的基本操作...................................................................................................165 6.2 图的存储结构...............................................................................................................166 6.2.1邻接矩阵............................................................................................................167 6.2.2 邻接表...............................................................................................................172 6.3 图的遍历.......................................................................................................................185 6.3.1 深度优先遍历...................................................................................................185 6.3.2 广度优先遍历...................................................................................................188 6.4 图的应用.......................................................................................................................189 6.4.1 最小生成树.......................................................................................................189 6.4.2 最短路径...........................................................................................................199 6.4.3 拓扑排序...........................................................................................................207 本章小结...............................................................................................................................210 习题六..................................................................................................................................210 第7章排序.......................................................................................................................213 7.1 基本概念.......................................................................................................................213 7.2 简单排序方法...............................................................................................................214 7.2.1 直接插入排序...................................................................................................214 7.2.2 冒泡排序...........................................................................................................216 7.2.3 简单选择排序...................................................................................................217 7.3 快速排序.......................................................................................................................219 7.4 堆排序...........................................................................................................................222 7.5 归并排序.......................................................................................................................230 7.6 基数排序.......................................................................................................................232 7.6.1 多关键码排序...................................................................................................232 7.6.2 链式基数排序...................................................................................................233 7.7 各种排序方法的比较与讨论.......................................................................................235 7.8 C#中排序方法...............................................................................................................235 本章小结...............................................................................................................................236 习题七..................................................................................................................................236 第8章查找.......................................................................................................................238 8.1 基本概念和术语............................................................................................................238 8.2 静态查找表...................................................................................................................238 8.2.1 顺序查找...........................................................................................................238 8.2.2 有序表的折半查找...........................................................................................239 8.2.3 索引查找...........................................................................................................242 8.3 动态查找表...................................................................................................................243 8.4 哈希表...........................................................................................................................252 8.4.1 哈希表的基本概念...........................................................................................252 8.4.2 常用的哈希函数构造方法...............................................................................253 8.4.3 处理冲突的方法...............................................................................................254 8.5 C#中的查找方法...........................................................................................................256 本章小结...............................................................................................................................256 习题八..................................................................................................................................256 参考文献......................................................................................................................................257

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值