.net core 数据结构与算法 1-1

本节内容为顺序表

简介

线性表是简单、基本、常用的数据结构。线性表是线性结构的抽象 (abstract),线性结构的特点是结构中的数据元素之间存在一对一的线性关系。这 种一对一的关系指的是数据元素之间的位置关系,即:

  • 除第一个位置的数据元素外,其它数据元素位置的前面都只有一个数据元素;
  • 除后一个位置的数据元素外,其它数据元素位置的后面都只有一个元素。也就是说,数据元素是 一个接一个的排列。因此,可以把线性表想象为一种数据元素序列的数据结构。

本节我们对线性表中的顺序表进行一个讲解。

保存线性表简单、自然的方式,就是把表中的元素一个接一个地放进顺序的存储单元,这就是线性表的顺序存储(sequence storage)。线性表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素, 用这种方式存储的线性表叫顺序表(sequence list)。说的明确一些也就是说,顺序表就是我们所接触过的数组。

线性表接口ilistds<t>

我们首先对我们会涉及到的函数进行一次封装,打包进线性表的接口中

interface ilistds<t>
{
    int getlength();//求长度,我们也可以通过属性进行操作
    void clear();//清空操作 
    bool isempty();//判断线性表是否为空          
    void append(t item);//向后追加操作
    void insert(t item, int i);//插入操作          
    t delete(int i);//删除操作          
    t getelem(int i);//取表元         
}

对于c语言,我们很多需要使用函数进行操作,但是在c#中,我们有索引器,属性等一系列语法糖,因此我们在后面操作的时候会把这些都展示给你们看。

事实上在我们之前的c#初级教程中的综合练习,就是关于我们的线性表操作,你可以返回去参考你的代码。

顺序表类seqlist<t>

为了方便起见,我们在此处不做可变长度的线性表,如果需要使用可变长度,这里有那么一种思路供读者思考:定义一个字段为容量(cap),一个为长度(len),长度是以及存储的空间大小,容量是总空间,如果长度和容量相等的时候,证明我们的表已经满了,那么就向后追加空的数组即可。

不过在这里我们不讨论这种,我们仅仅使用定长的就足够展示了

class seqlist<t>:ilistds<t>
{
    private int length;//长度
    private int size;
    private int lastindex;//最后一个元素的索引
    private t[] data;  
    public int length{get{return lastindex+1;}}
    //初始化
    public seqlist<t>(int size)
    {
        this.data = new t[size];
        this.lastindex = -1;
        this.size = size;
    }
    //清除内部元素
    public void clear()
    {
        this.data = new t[this.size];
        lastindex = -1;
    }
    //判断是否为空,只需要判断最后一个元素的索引是否为-1即可
    public bool isempty()
    {
        return lastindex==-1?true:false;
    }
    //获取长度
    public int getlength()
    {
        return lastindex + 1;
    }
    //是否已满
    public bool isfull()
    {
        return size == lastindex+1?true:false;
    }
    //向后追加
    public void append(t item)
    {
        //只需要判断是否已满即可
        if(!isfull())
        {
            data[lastindex++] = item;
        }
        else
        {
            console.writeline("it is full");
        }
    }
    //在指定位置插入,index指代位置而不是索引
    public void insert(t item,int index)
    {
        //首先判断是否已满        
        if(isfull())
        {
            console.writeline("it is full");
            return;
        }
        if(index<1||index>lastindex+2)
        {
            console.writeline("error");
            return;
        }
        //最后一位插入
        if (i == last +2)
        {      
            data[i-1] = item; 
        }
        else
        {
            //元素移动
            for (int j = lastindex; j>= i-1; --j)
            {                     
                data[j + 1] = data[j];                
            }
            data[i-1] = item; 
        }
        lastindex++;
    }
    public t delete(int i)         
    {             
        t tmp = default(t); 
 
            //判断表是否为空             
            if (isempty())             
            {                 
                console.writeline("list is empty");      return tmp;             
            } 
 
            //判断删除的位置是否正确             
            // i小于1表示删除第1个位置之前的元素,
            // i大于last+1表示删除后一个元素后面的第1个位置的元素。             
            if (i < 1 || i > lastindex+1)             
            {                 
                console.writeline( "position is error!");return tmp; 
            } 
            //删除的是后一个元素             
            if (i == lastindex+1)             
            {                 
                tmp = data[last--];   
            }  
            //删除的不是后一个元素
            else               
            {                
                //元素移动                 
                tmp = data[i-1];                 
                for (int j = i; j <= lastindex; ++j)
                {                     
                    data[j] = data[j + 1];      
                }             
            } 
            //修改表长             
            --lastindex;             
            return tmp;     
        }
        public t getelem(int i)
        {
            if(i<1||lastindex==-1)
            {
                console.writeline("error");
                return;
            }
            return this.data[i-1];
        }
}

在上述代码中,我们分析一下删除和插入的操作

算法的时间复杂度分析:顺序表上的删除操作与插入操作一样,时间主要消 耗在数据的移动上在第i个位置删除一个元素,从ai+1到an都要向前移动一个位置,共需要移动n-i个元素,而i的取值范围为 1≤i≤n,当i等于 1 时,需要移动 的元素个数多,为n-1 个;当i为n时,不需要移动元素。设在第i个位置做删除 的概率为pi,则平均移动数据元素的次数为(n-1)/2。这说明在顺序表上做删除操 作平均需要移动表中一半的数据元素,所以,删除操作的时间复杂度为o(n)。

一些额外操作

我们以倒转为例,事实上我们倒转的时候,只需要将第一个和最后一个,第二个和倒数第二个以此类推进行交换即可

public void reversseqlist(seqlist<int> l)         
{             
    int tmp = 0;             
    int len = l.getlength();             
    for (int i = 0; i<= len/2; ++i)             
    {                 
        tmp = l[i];                 
        l[i] = l[len - i];                 
        l[len - i] = tmp;             
    }     
}

各位可以尝试一下生成不含重复值顺序表和合并顺序表并有序的排列算法,这里给出一些思路。

  • 去重:先把顺序表 la 的第 1 个元素赋给顺序表 lb,然后从顺序表 la 的第 2 个元素起,每一个元素与顺序表 lb 中的每一个元素进行比较,如果不相 同,则把该元素附加到顺序表 lb 的末尾。
  • 合并排序:依次扫描 la 和 lb 的数据元素,比较 la 和 lb 当前数据元素的 值,将较小值的数据元素赋给 lc,如此直到一个顺序表被扫描完,然后将未完 的那个顺序表中余下的数据元素赋给 lc 即可。lc 的容量要能够容纳 la 和 lb 两个表相加的长度。

github

bilibili主页

warrenryan’s blog