C#数据结构(1) 顺序表、栈与队列

时间:2019-03-19
本文章向大家介绍C#数据结构(1) 顺序表、栈与队列,主要包括C#数据结构(1) 顺序表、栈与队列使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

导言

大一下学期的数据结构课使用的语言是C#,这是一门以前我从来没有接触过的语言。不过凭着自己还有点C++的底子,硬是花了两个晚上把课上可能要用到的内容啃完了。至于.NET相关的部分,暂时用不到,也就暂时没有去碰了。(压力是唯一的动力,对于我这种懒惰的人而言233333)

个人对C#的评价,就是一门介于C++和JAVA之间的语言。纯面向对象、所有函数都包含在类当中的架构,确确实实地体现了C#为.NET而生的本质:一门做UI的语言。C#个人感觉把面向对象的模式发挥到了极致,当然也带来了一些依赖性问题,这些暂且就不在我们的讨论范围内了。我们只需要知道,对面向对象的强烈支持,确实能够让我们更加容易地做出各种容器,我想这大概也是老师选用C#来上数据结构课的原因。

那么首先最简单的数据结构就是变长数组,也就是顺序表。我们的目的是要制作一个数据结构,内部维护一个数组,可以在数组内增删改找数据,并且在数据增减时自动调整数组的大小。

总结一下,一个该顺序表实例拥有以下功能:

  • 使用数组或其他该顺序表实例初始化
  • 以下标方式访问元素
  • 获取目前拥有的元素个数
  • 在任意位置加入任意个数元素
  • 覆盖任意位置的任意个数元素
  • 删除任意位置的任意个数元素
  • 从左到右或从右到左查找元素并返回下标
  • 排序

1.1核心成员

在C#类库项目中,首先新建一个名叫DSAGL的namespace,在其中包含一个泛型类SequencedList(C#中的泛型就是T,简单来说就是可以把这个T在类实例化的时候替换成任何类型)。这个泛型类中的泛型继承自接口IComparable,这就要求这里面储存的泛型必须是可比较的,使得内建排序算法成为可能。

namespace DSAGL
{
      public class SequencedList<T> where T : IComparable
      {
      }
}

类中含有三个成员,一个是我们顺序表的核心数组content,一个是顺序表的长度length,即目前储存的元素个数,最后一个max则是目前顺序表最多能储存的元素个数,它是128的整数倍。当元素个数超过max时,max会被增加128的整数倍,核心数组content的长度也会被增加同样的数值。同样地,当元素个数减少到“max-(128的整数倍)”以下时,max与核心数组content的长度也会被相应减少。

三个成员都被声明为私有,以符合OOP的精神~

private T[] content;
private int length;
private int max;

1.2基本属性

C#的类所拥有的非常强大的功能就是属性和索引器。前者相当于C++中一些进行返回或设置成员变量的值的简单操作的内联函数,后者相当于对[]的重载。我们提供只读的getLength属性以便外界读取length成员,同时保持对length的封装。另外提供索引器来使得外界可以通过访问普通数组的方式来访问容器内的数组

public int getLength//获取长度
{
     get { return length; }
}

public T this[int i]//顺序表的索引器
{
      get
      {
            if (i < 0 || i >= length)
                  return default(T);
            return content[i];
      }
      set
      {
            if (i >= 0 && i < length)
                  content[i] = value;
      }
}

可以看到索引器对于一些非法的访问进行了屏蔽,以提高容器安全性。

1.3构造函数

SequencedList提供了三种构造函数,分别是默认的空初始化、数组初始化和利用SequencedList的初始化

public SequencedList()//默认初始化
{
       content = new T[128];
       length = 0;
       max = 128;
}
public SequencedList(SequencedList<T> tar)//使用SequencedList初始化
{
      content = new T[(tar.getLength / 128 + 1) * 128];
      for (int i = 0; i < tar.getLength; i++)
           content[i] = tar[i];
      length = tar.getLength;
      max = (tar.getLength / 128 + 1) * 128;
}
public SequencedList(T[] tar)//使用数组或数据列表初始化
{
     content = new T[(tar.Length / 128 + 1) * 128];
     for (int i = 0; i < tar.Length; i++)
          content[i] = tar[i];
     length = tar.Length;
     max = (tar.Length / 128 + 1) * 128;
}

对于默认初始化,只需要为数组分配默认最小的128个元素的空间,并将max调成默认最小的128,将length更改为容器空时的0即可。而在使用数组和SequencedList初始化的时候,则要根据目标大小调整content的尺寸、max的数值。容器的length将等于目标的中含有的元素个数。

1.4 resize函数

这个函数的作用很明了,也很重要,它是实现变长数组的核心。通过新建一个128*rank大小的数组并把原数组的数据复制到新数组当中来实现数组大小的变化,而在没有delete关键词的C#中,原数组会在一段时间内自动被C#的垃圾回收器回收。

private void resize(int rank)
{
      T[] newContent = new T[128 * rank];
      Array.Copy(content, newContent, length);
      content = newContent;
      max = 128 * rank;
}

1.5添加元素函数

插入元素一共提供了支持单个元素插入、从SequencedList插入和从数组插入三个版本,每个版本又有重载,共计8个函数。。。

public void add(T tar)//在容器尾加入元素
{
      int pos = length;
      if (pos >= max - 1)
            resize(max / 128 + 1);
      length += 1;
      for (int i = length; i > pos; i--)
            content[i] = content[i - 1];
      content[pos] = tar;
}

public void add(T tar, int pos)//在pos前插入元素
{
      if (pos < 0 && pos >= length)
            return;
      length += 1;
      for (int i = length; i > pos; i--)
            content[i] = content[i - 1];
      content[pos] = tar;
}    

public void add(T[] tar)//从数组复制并在容器尾加入元素
{
      int startOfMe = length,num=tar.Length,startOfTar=0;
      if (length + num >= max - 1)
            resize((length + num) / 128 + 1);
      length += num;
      for (int k = startOfMe; k < length - num; k++)
            content[k + num] = content[k];
      int i = 0, j = 0;
      while (j < num)
            content[startOfMe + i++] = tar[startOfTar + j++];
}

public void add(T[] tar,int startOfMe, int startOfTar = 0)//从数组startOfTar位置开始复制并在startOfMe之前插入元素
{
      int num = tar.Length;
      if (startOfMe < 0 && startOfMe >= length)
            return;
      if (startOfTar < 0 || startOfTar >= tar.Length)
            return;
      if (length + num >= max - 1)
            resize((length + num) / 128 + 1);
      length += num;
      for (int k = startOfMe; k < length - num; k++)
            content[k + num] = content[k];
      int i = 0, j = 0;
      while (j < num)
            content[startOfMe + i++] = tar[startOfTar + j++];
}

public void add(T[] tar, int startOfMe,int startOfTar,int num)//从数组startOfTar位置开始复制num个元素并在startOfMe之前插入元素
{
      if (startOfMe < 0 && startOfMe >= length)
            return;
      if (startOfTar < 0 || startOfTar >= tar.Length)
            return;
      if (num > tar.Length - startOfTar)
            num = tar.Length - startOfTar;
      if (length + num >= max - 1)
            resize((length + num) / 128 + 1);
      length += num;
      for (int k = startOfMe; k < length - num; k++)
            content[k + num] = content[k];
      int i = 0, j = 0;
      while (j < num)
            content[startOfMe + i++] = tar[startOfTar + j++];
}

public void add(SequencedList<T> tar)//从SequencedList复制并在容器尾加入元素
{
      int startOfMe = length, num = tar.getLength, startOfTar = 0;
      if (length + num >= max - 1)
            resize((length + num) / 128 + 1);
      length += num;
      for (int k = startOfMe; k < length - num; k++)
            content[k + num] = content[k];
      int i = 0, j = 0;
      while (j < num)
            content[startOfMe + i++] = tar[startOfTar + j++];
}

public void add(SequencedList<T> tar, int startOfMe, int startOfTar = 0)//从SequencedList的startOfTar位置开始复制并在startOfMe之前插入元素
{
      int num = tar.getLength;
      if (startOfMe < 0 && startOfMe >= length)
            return;
      if (length + num >= max - 1)
            resize((length + num) / 128 + 1);
      length += num;
      for (int k = startOfMe; k < length - num; k++)
            content[k + num] = content[k];
      int i = 0, j = 0;
      while (j < num)
            content[startOfMe + i++] = tar[startOfTar + j++];
}

public void add(SequencedList<T> tar, int startOfMe, int startOfTar, int num)//从SequencedList的startOfTar位置开始复制num个元素并在startOfMe之前插入元素
{
      if (startOfMe < 0 && startOfMe >= length)
            return;
      if (startOfTar < 0 || startOfTar >= tar.getLength)
            return;
      if (num > tar.getLength - startOfTar)
            num = tar.getLength - startOfTar;
      if (length + num >= max - 1)
            resize((length + num) / 128 + 1);
      length += num;
      for (int k = startOfMe; k < length - num; k++)
            content[k + num] = content[k];
      int i = 0, j = 0;
      while (j < num)
            content[startOfMe + i++] = tar[startOfTar + j++];
}

插入num个元素的方法就是将startOfTar位置及以后的num个元素向后移动num位,再覆盖startOfTar到startOfTar+num中间的所有元素。计算操作次数的数学期望:平均每次要移动1n+1\frac {1} {n+1}个元素,通过等差数列求和可知每个元素要移动次数的期望是n(n+1)2\frac {n(n+1)}{2},因此数学期望就是1n+1n(n+1)2=n2\frac {1} {n+1}·\frac {n(n+1)}{2}=\frac {n}{2}顺序表插入元素的时间复杂度是o(n)

1.6覆盖元素函数

覆盖元素的方法比插入元素更加简单,省去了原有元素的位移操作,需要注意的只是length、max值以及content大小的改变而已

public void cover(T[] tar)//从数组复制并覆盖元素
{
      int startOfMe = length, num = tar.Length,startOfTar=0;
      int j = 0, i = 0;
      while (j < num)
      {
            if (startOfMe + j >= max - 1)
                  resize((startOfMe + j) / 128 + 1);
            if (startOfMe + j >= length)
                  length = startOfMe + j + 1;
            content[startOfMe + i++] = tar[startOfTar + j++];
      }
}
      

public void cover(T[] tar, int startOfMe, int startOfTar = 0)//从数组startOfTar位置开始复制并从startOfMe开始覆盖元素
{
      int num = tar.Length;
      if (startOfMe < 0 && startOfMe >= length)
            return;
      if (startOfTar < 0 || startOfTar >= tar.Length)
            return;
      int j = 0, i = 0;
      while (j < num)
      {
            if (startOfMe + j >= max - 1)
                  resize((startOfMe + j) / 128 + 1);
            if (startOfMe + j >= length)
                  length = startOfMe + j + 1;
            content[startOfMe + i++] = tar[startOfTar + j++];
      }
}

public void cover(T[] tar, int startOfMe, int startOfTar, int num)////从数组startOfTar位置开始复制并从startOfMe开始覆盖num个元素
{
      if (startOfMe < 0 && startOfMe >= length)
            return;
      if (startOfTar < 0 || startOfTar >= tar.Length)
            return;
      if (num > tar.Length - startOfTar)
            num = tar.Length - startOfTar;
      int j = 0, i = 0;
      while (j < num)
      {
            if (startOfMe + j >= max - 1)
                  resize((startOfMe + j) / 128 + 1);
            if (startOfMe + j >= length)
                  length = startOfMe + j + 1;
            content[startOfMe + i++] = tar[startOfTar + j++];
      }
}

public void cover(SequencedList<T> tar)//从SequencedList复制并覆盖元素
{
      int startOfMe = length, num = tar.getLength, startOfTar = 0;
      int j = 0, i = 0;
      while (j < num)
      {
            if (startOfMe + j >= max - 1)
                  resize((startOfMe + j) / 128 + 1