• 数据结构基础温故-1.线性表(上)


    开篇:线性表是最简单也是在编程当中使用最多的一种数据结构。例如,英文字母表(A,B,C,D...,Z)就是一个线性表,表中的每一个英文字母都是一个数据元素;又如,成绩单也是一个线性表,表中的每一行是一个数据元素,每个数据元素又由学号、姓名、成绩等数据项组成。顺序表链表作为线性表的两种重要的存在形式,它们是堆栈、队列、树、图等数据结构的实现基础。

    一、线性表基础

    1.1 线性表的基本定义

      线性表:零个或多个数据元素的有限序列。线性表中的元素在位置上是有序的,类似于储户去银行排队取钱,人们依次排着队,排在前面的先取,排在后面的则后取。这种位置上的有序性就是一种线性关系。由此可以看出:线性表的前后两个元素存在一一对应关系

    PS:需要注意的是,这种前后关系是逻辑意义上而非物理意义上的,就好比如果银行做了改革,使用排队机进行排队,所有储户分散在银行的各个角落,他们取钱的顺序是根据储户从排队机获取的纸条上的号码来决定的。

    1.2 线性表的存储结构

      (1)顺序表

      线性表的顺序存储结构是指【用一块地址连续的存储空间依次存储线性表中的数据元素】。就好像我们刚刚提到的改革之前的银行,需要在业务窗口前排队等候办理。由此可以看出:在顺序表中,逻辑上相邻的元素在物理上也是相邻的

      (2)链表

      相比顺序表需要预先占用一块事先分配好的存储空间,链表就灵活一些。链表中逻辑上相邻的元素在物理上可以不相邻。这就好像改革之后的银行,人们办理业务的顺序是由手上的小纸条的号码来决定。在某些特定场合,链表的使用优先于顺序表。

    二、顺序表基础

    2.1 静态顺序表之数组

      在日常编程中,在处理一组数据时,最常使用的数据类型就是数组。它是线性表的顺序存储结构在程序语言中最直接的表现形式

      数组是最基础也是存取速度最快的一种集合类型,在.NET中它是引用类型,也就是说它所需的内存空间会在托管堆上分配,一旦数组被创建,其中的所有元素会被初始化为它们的默认值。

    PS:另外需要注意的是,当数组元素为值类型时,数组对象存放的是值类型对象本身。而当元素为引用类型时,数组对象存放的则是对象的引用(指针)。

      (1)数组元素为值类型时:

    int[] arrInt = new int[5];
    arrInt[2] = 5;
    arrInt[4] = 3;

      下图展示了上面的数组arrInt在内存(这里如未说明都指在.NET中的内存分配)中的分配形式,可以看到值类型数组在被创建的同时就拥有了默认值0。

      (2)数组元素为引用类型时:

    // System.Windows.Forms.Control
    Control[] arrCtrl = new Control[5];
    arrCtrl[0] = new Button();
    arrCtrl[3] = new Label();

      下图则展示了上面的数组arrCtrl在内存中的分配,可以看到在托管堆中划分了一块能够存放5个指针的内存区域,并且每个元素都被初始化为null。如果某个元素被赋值,那么会存放一个指向实际对象存储区域的指针。

    总结:数组优点很多,缺点也很明显:在实际编程中,无法动态改变集合的大小

    2.2 动态顺序表之ArrayList与List<T>

      如果需要动态地改变数组所占用的内存空间的大小,则需要以数组为基础做进一步的抽象以实现这个功能。在C#中,ArrayList被称为动态数组,它的存储空间可以被动态地改变,同时还有添加、删除元素的功能。

      (1)简单好用但不是类型安全的ArrayList

      ①Add-添加新元素

            // 在数组末尾顺序添加指定元素
            public virtual int Add(object value)
            {
                // 当容量达到最大值时
                if (this._size == this._items.Length)
                {
                    // 调整存储空间大小
                    this.EnsureCapacity(this._size + 1);
                }
    
                this._items[this._size] = value;
                return this._size++;
            }    

      可以看到,在添加新元素时会进行数组容量的判断,如果达到最大值则会调用方法动态调整数组大小。

      ②RemoveAt-移除指定元素

            // 移除指定索引的元素
            public virtual void RemoveAt(int index)
            {
                if (index < 0 || index > this._size)
                {
                    throw new ArgumentOutOfRangeException("index", "索引超过范围");
                }
                // 插入位置后的元素向前移动一位
                for (int i = index + 1; i < this._size; i++)
                {
                    this._items[i - 1] = this._items[i];
                }
    
                this._size--;
                this._items[this._size] = null; // 最后一位空出的元素置为空
            }

      可以看到,在移除老元素时会进行大量的元素移动操作。这里的ArrayList采用的元素类型是object,所以最后将空出的元素置为null。

      ③EnsureCapacity-动态调整数组大小

            // 动态调整数组空间大小
            private void EnsureCapacity(int min)
            {
                if (this._items.Length < min)
                {
                    // 新空间大小=原空间大小*2
                    int num = (this._items.Length == 0) ?
                        _defaultCapacity : (this._items.Length * 2);
                    if (num < min)
                    {
                        num = min;
                    }
                    // 调整数组空间大小
                    this.Capacity = num;
                }
            }

      事实上,内存空间一旦分配是没有办法更改大小的。ArrayList其实使用“搬家”的方法来实现这个功能的,即当房子住不下这么多人的时候,那么换一个更大的新房子就行了。这里,ArrayList需要扩容时,会在内存空间中开辟一块新区域,容量为原来的2倍,并把所有元素都复制到新内存空间中。

      (2).NET2.0出现的泛型版本:List<T>

      由于ArrayList实际存放的是object对象(在.NET中object是万物之宗,即所有类型的父类),在进行存取操作时需要进行大量的装箱和拆箱操作(如果你不知道装箱和拆箱,那么请阅读.NET中六个重要的概念),降低程序性能。于是,从.NET 2.0开始出现了泛型版本的List<T>,它完美取代了ArrayList。

    List<int> intNumList = new List<int>();
    intNumList.Add(1);
    intNumList.Add(2);
    
    int num1 = intNumList[0];
    int num2 = intNumList[1];

      可以看到,在集合创建的时候就把元素类型限定为int类型,它是安全的,并且还避免了装箱和拆箱操作。因此,在实际编程中一般都会使用List<T>。

    三、.NET中的ArrayList与List<T>

      在.NET中已经为我们提供了两个强有力的顺序表结构类型,我们可以通过Reflector来查看其具体实现。

    3.1 ArrayList的实现

      通过查看源码,其关键就在于EnsureCapacity方法动态地调整数组大小。

    3.2 List<T>的实现

      通过查看源码,其关键就在于使用了泛型,而其他的方法如Add、Remove以及EnsureCapacity都和ArrayList没有多大区别。

    参考资料

    (1)程杰,《大话数据结构》

    (2)陈广,《数据结构(C#语言描述)》

    (3)段恩泽,《数据结构(C#语言版)》

  • 相关阅读:
    cf 1155 d 最大区间和(变形 区间*x)
    俄罗斯方块的形状暴力
    cf 1160 E dp 组合数 思维
    cf 1110d dp(题目特殊性质)
    cf 1114d 区间dp 0,1标记左右
    poj 1426 bfs
    poj 1679 最小生成树是否唯一
    cf 1106e dp
    【PAT顶级】1002 Business (35分)(0/1背包,DP)
    【PAT顶级】1001 Battle Over Cities
  • 原文地址:https://www.cnblogs.com/edisonchou/p/4593972.html
Copyright © 2020-2023  润新知