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中间的所有元素。计算操作次数的数学期望:平均每次要移动个元素,通过等差数列求和可知每个元素要移动次数的期望是,因此数学期望就是,顺序表插入元素的时间复杂度是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
- (cljs/run-at (JSVM. :all) "一起实现柯里化")
- (cljs/run-at (JSVM. :browser) "简单类型可不简单啊~")
- 前端魔法堂:解秘FOUC
- JS魔法堂:深究JS异步编程模型
- 前端魔法堂:屏蔽Backspace导致页面回退
- “表情包”火爆全球,域名emojis.com小六位易主
- 前端魔法堂:onsubmit和submit事件处理函数怎么不生效呢?
- (cljs/run-at (JSVM. :all) "Metadata就这样哦")
- (cljs/run-at (JSVM. :all) "细说函数")
- 动手写个数字输入框2:起手式——拦截非法字符
- Linux安装DNSmasq搭建自己的公共DNS
- (cljs/run-at (JSVM. :all) "一次说白DataType、Record和Protocol")
- 虚拟主机多域名转向
- .top域名名声大噪,这枚单字符10天建站上线!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- JS破解初探,折腾到头秃的美拍视频采集下载
- 去哪儿景点信息爬取并使用Django框架网页展示
- Kubernetes v1.15.3 升级到 v1.18.5 心得
- 结巴分词seo应用,Python jieba库基本用法及案例参考
- nali一个可以查询IP归属和CDN的命令
- 图片采集,python多线程采集头像图片源码附exe程序及资源包
- Python json数据爬取处理,红点官网大奖设计作品爬取
- 斗图狂魔必备沙雕表情包,python多线程爬取斗图啦表情图片
- 5个基本Linux命令行工具的现代化替代品
- Chrome 84 正式发布,支持私有方法、用户空闲检测!
- 类及数据库的应用,G-MARK网站数据Python爬虫系统的构建
- 获取素材图无忧,Pixabay图库网Python多线程采集下载
- Python关键词数据采集案例,5118查询网站关键词数据采集
- Python结巴分词,字符串余弦相似度算法实现关键词筛选及整理
- git的分支远程连接和远程分支的拉取推送及冲突处理