(待补充)常用数据结构学习

1. 数据结构:相互之间存在一种或多种特定关系的数据元素的集合。形式定义:数据结构是一个二元组(D,S),D是数据元素的有限集,S是D上关系的有限集。

2. 结构:数据元素之间的关系称为结构,通常由四种基本结构:集合、线性结构、树形结构、图状(网状)结构;

3. 数据结构在数学层面的描述是逻辑结构,在计算机中的表示(映像)是物理(存储)结构。

4. 数据元素在计算机中表示为元素或结点,当一个数据元素由若干个数据项组成的时候,结点中对应于各个数据项的子位串称为数据域。

5. 数据元素之间的关系在计算机中分为:顺序映像和非顺序映像;

由此得到的两种存储结构:顺序存储结构和链式存储结构。

6. 数据类型:是一个值的集合和定义在这个值集上的一组操作的总称。如整型变量的值集是某个区间上的整数,定义在其上的操作是加减乘除和取模等算术运算。

7. 抽象数据类型:指一个数学模型以及定义在该模型上的一组操作。按值的不同特性可分为三种类型:原子类型、固定聚合类型、可变聚合类型。形式定义:抽象数据类型是一个三元组(D,S,P),D是数据对象,S是D上的关系集,P是对D的基本操作集。

 

  • 线性表

1. 定义:一个线性表是n个数据元素的有限序列。数据元素依具体应用情况而定,可以是基本类型也可以是由若干个数据项组成的记录,含有大量记录的线性表又称文件。

2. 特点:线性表中元素个数n(n>=0)定义为长度,n=0时称为空表。在非空表中每个数据元素都有一个确定的位置。

3. 操作:查找、排序、添加、删除

4. 结构:顺序结构(数组)、链式结构(结点)

 

1. 顺序表:用一组地址连续的存储单元依次存储线性表的数据元素。

2. 链式表:用一组任意的存储单元存储线性表的数据元素(可以连续也可以不连续)。

3. 静态链表:用数组描述的链式表。

4. 循环链表:特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。所以从表中任何一个结点出发都能找到其它结点。

5. 双向链表:结点中有两个指针域,一个指向前驱,一个指向后继。

  • 栈和队列:

1. 栈和队列是特殊的线性表,但是不同的抽象数据类型。

2. 栈:是限定近在表尾(即栈顶)进行插入或删除操作(后进先出)的线性表。

3. 队列:先进先出的线性表,只允许在表一端(队尾)插入,在另一端(队头)删除元素

  • 串:

1. 串(或字符串)是由零个或多个字符组成的有限序列,一般记为:s=’a1a2a3…an’ (n>=0) s是串名,单引号里面的字符序列是串值,ai可以是字母、数字或其它字符,串中字符的数目称为串的长度。长度为0的串是空串。

2. 串中任意连续个字符组成的子序列是该串的子串。

3. 两个串相等=串长相等&&串中对应位置的字符相同

4. 串的定长顺序存储表示:类似于顺序表,用一组地址连续的存储单元存储串值,存储空间大小在最开始指定为固定值,如此在操作过程中如果出现串长超限的情况就统一用‘截尾法’处理。

5. 串的堆分配存储表示:仍用一组地址连续的存储单元存放串值,但它们的存储空间是在程序执行过程中动态分配的。

6. 串的块链存储表示:用链表方式存储,每个结点可以存储一个或多个字符,当存储多个字符时,存在最后一个结点不满的情况,可用“#”来表示空字符

  • 数组和广义表

1. 数组和广义表可以看成是线性表的扩展,即表中的数据元素本身也是一个数据结构,如n维数组可以看作数据元素是n-1维数组类型的一维数组类型。

2. 数组的顺序存储表示:用一维数组按约定次序(一般为行序)来表示多维数组。

3. 广义表:或称列表lists,表中每个元素可以是单个元素(原子)或一个广义表(子表),第一个元素称表头,剩下所有元素称为表尾。

广义表的存储结构:通常为链式,每个数据元素可用一个结点表示。

树和二叉树

  1. 树:是n(n>=0)个结点的有限集。树的结点包含一个数据元素以及若干指向其子树的分支。结点拥有的子树个数称为该结点的度。度为0(没有子树)的结点称为叶子(或终端结点),度不为0的结点称为分支结点。树的度是各结点度的最大值。结点的层次从根开始定义,根为第一层。结点的最大层次称为树的深度(高度)。
  2. 有序树、无序树:结点的子树从左到右有无次序。
  3. 森林:是m(m>=0)棵互不相交的树的集合。非空树根节点的各个子树的集合就是森林。故可将树看作二元组Tree=(root,F),root是根节点,F是子树组成的森林。
  4. 二叉树:特点是每个结点至多只有两棵子树,并且子树有左右之分,次序不能颠倒(有序树)。
  5. 二叉树的性质:(1)在二叉树的第i层上至多有2^(i-1)个结点(i>=1);(2)深度为k的二叉树至多有(2^k)-1个结点(k>=1);(3)任意二叉树,如果其终端结点的个数为a,度为2的结点个数为b,则a=b+1;
  6. 满二叉树:深度为k且有(2^k)-1个结点的二叉树
  7. 完全二叉树:结点是从上到下、从左到右顺序排列的二叉树;
  8. 二叉树的顺序存储结构:用一组地址连续的存储单元(一维数组)依次自上而下,从左到右存储完全二叉树的结点元素,仅适用于完全二叉树。
  9. 二叉树的链式存储结构:二叉链表(数据域+左、右结点)、三叉链表(数据域+左、右结点+父结点)、线索链表

查找

(1)静态查找表:表中元素个数不再改变,只对表进行查找或排序操作,分类:

1.顺序查找:从表尾元素开始向前查找,平均查找长度(n+1)/2,可以通过将查找频率高的元素后移来提高查找效率;

2.二分查找(有序表):每次查找将范围缩小一半,直到最后一个元素或找到为止,平均查找长度log2(n+1)-1

Int Search(SSTable ST, KeyType key) {
  //在有序表ST中折半查找其关键字等于key的数据元素。若找到,则函数值为
  //该元素在表中的为止,否则为-1
  Low=1; high=ST.length; //区间初值
  While(low<=high) {
    Mid=(low+high)/2;
If(key==ST.elem[mid].key) return mid;
Else if (key<ST.elem[mid].key) high=mid-1;
Else low=mid+1;
}
Return -1;

3.分块查找:建立索引顺序表,表中记录该分块的最大元素值和起始地址(及长度),索引表有序,块内无序,所以对索引表可以用顺序查找或二分查找,而对块则需用顺序查找。平均查找长度为((n/s + s)+1)/2(顺序查找)、log2 (n/s + 1) + s/2(二分查找)。

 

  1. 动态查找表:表中元素个数变化,可以对表进行插入删除查找排序的操作。

二叉排序树:左子树结点值均小于根节点值,右子树值均大于根节点值。

Int Search(BiTree T,KeyType key,BiTree f,BiTree &p) {
  //在根指针T所指二叉排序树中递归查找其关键字==key的元素,若查找成功则
  //指针p指向该结点并返回TRUE,否则指针p指向查找路径上访问的最后一个
  //结点并返回FALSE,指针f指向T的双亲,其初始值为NULL;
  If(!T) {p=f; return FALSE;}	//查找不成功
  Else if (key==T->data.key) {p=T; return TRUE;}
  Else if (key<T->data.key) return Search(T->lchild,key,T,p);
  Else return Search(T->rchild,key,T,p);
} //查找操作,成功则返回对应结点,否则返回叶子结点

Int Insert(BiTree &T,ElemType e) {
  //当二叉排序树T中不存在关键字=e.key的元素时,插入e并返回TRUE
  If(!Search(T,e.key,NULL,p) {
    S=new BiTree();
S->data=e; s->lchild=s->rchild=NULL;
        If(!p) T=s;		//原本是空树,则s为根结点
Else if(e.key<p->data.key) p->lchild=s;
Else p->rchild=s;
Return TRUE;
}
Else retrun FALSE;
} //插入操作

Int Delete(BiTree &T, KeyType key) {
      //若二叉排序树T中存在关键字==key的元素,则删除该元素结点并返回TRUE
  If(!T) return FALSE;  //不存在关键字==key的元素
  Else {
    If(key==T->data.key) {
  //从二叉排序树中删除结点T并重接它的左右子树
  If(!p->rchild) { //右子树空则只需重接它的左子树
    q=T;T=T->lchild;free(q);
}
Else if(!p->lchild) { //左子树空则只需重接它的右子树
 	q=T;T=T->rchild;free(q);
}
Else { //左右子树都不空
  q=T; s=T->lchild;
  While(s->rchild) {q=s; s=s->rchild;}
T->data=s->data;
If(q!=T)q->rchild=s->lchild;
Else q->lchild=s->lchild;
delete s;
}
}
Else if(key<T->data.key) return Delete(T->lchild,key);
Else return Delete(T->rchild,key);
  }
} //删除操作

平衡二叉树(AVL树):左右子树深度差的绝对值不超过1

 

哈希表:根据设定的哈希函数H(key)和处理冲突的方法将一组关键字映像到一个有限的连续的地址集上,并以关键字在地址集中的‘像’作为记录在表中的存储位置,这种表便被称为哈希表,这一映像过程称为哈希造表或散列,所存储位置称为哈希地址或散列地址。

 

排序

  1. 简单排序算法:时间复杂度O(n^2),直接插入排序、折半插入排序,冒泡排序,简单选择排序
  2. 先进排序算法:时间复杂度O(n*logn),快速排序、堆排序、归并排序
  3. 基数排序算法:时间复杂度O(d*n)

 

  1. 直接插入排序:将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表,时间复杂度O(n^2),空间复杂度O(1):
void InsertSort(vector<int> &vt)
{
    for(int i=2,j;i<vt.size();++i)
    {
        if(vt[i]<vt[i-1]){  //判断当前数据是否需要前移
            vt[0]=vt[i];    //设置vt[0]为哨兵
            vt[i]=vt[i-1];
            for(j=i-2;vt[j]>vt[0];--j)
                vt[j+1]=vt[j];  //记录后移
            vt[j+1]=vt[0];      //插入到正确位置
        }
    }
}
  1. 折半插入排序:将直接插入排序中的‘查找’操作采用‘折半查找’来实现。时间复杂度O(n^2),空间复杂度O(1):
void InsertSort(vector<int> &vt)
{
    for(int i=2,j;i<vt.size();++i)
    {
        if(vt[i]<vt[i-1]){  //判断当前数据是否需要前移
            vt[0]=vt[i];    //设置vt[0]为哨兵
            int low=1,high=i-1;
            while(low<=high){
                int mid=low+(high-low)/2;
                if(vt[mid]>vt[0])high=mid-1;
                else low=mid+1;
            }
            for(j=i-1;j>=high+1;--j)
                vt[j+1]=vt[j];  //记录后移
            vt[high+1]=vt[0];      //插入到正确位置
        }
    }
}
  1. 希尔排序:先将整个待排记录序列分割成为若干个子序列分别进行直接插入排序,待整个序列中的记录‘基本有序’时,再对全体记录进行一次直接插入排序。
void ShellSort(vector<int> &vt,vector<int> dlta)
{
    //dlta为增量序列,最后一个增量必须是1
    for(int k=0;k<dlta.size();++k)
    {
        for(int i=dlta[k]+1,j;i<vt.size();++i)
        {
            if(vt[i]<vt[i-dlta[k]])
            {
                vt[0]=vt[i];    //只做暂存数据,不是哨兵
                for(j=i-dlta[k];j>0&&vt[0]<vt[j];j-=dlta[k])
                    vt[j+dlta[k]]=vt[j];
                vt[j+dlta[k]]=vt[0];
            }
        }
    }
}
  1. 冒泡排序:将第一个记录的关键字与第二个记录的关键字进行比较,若为逆序则交换,然后比较第二个记录和第三个记录的关键字,以此类推。每趟冒泡排序都会将未排序列中最大的元素沉到最后。时间复杂度O(n^2),空间复杂度O(1):
void Sort(vector<int> &vt)
{
    for(int i=vt.size()-1;i>0;--i)
    {
        for(int j=0;j<i;++j)
        {
            if(vt[j]>vt[j+1])
            {
                int tp=vt[j];
                vt[j]=vt[j+1];
                vt[j+1]=tp;
            }
        }
    }
}
  1. 快速排序:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可对这两部分记录继续排序,依次达到整个序列有序。时间复杂度O(n*logn)(如果待排序列有序或基本有序,退化为冒泡排序,时间复杂度O(n^2),空间复杂度O(logn):
int Partition(vector<int> &vt,int low,int high)
{
    //对顺序表vt中子表r[low…high]进行排序
    int tp=vt[low];
    while(low<high)
    {
        while(low<high&&vt[high]>=tp)--high;
        vt[low]=vt[high];//从后往前找到比枢轴小的元素赋值给low位置
        while(low<high&&vt[low]<=tp)++low;
        vt[high]=vt[low];//从前往后找到比枢轴大的元素赋值给high位置
    }
    vt[low]=tp;//将枢轴赋值到位
    return low;//返回枢轴
}

void QSort(vector<int> &vt,int low,int high)
{
    if(low<high)
    {
        int pivot=Partition(vt,low,high);
        QSort(vt,low,pivot-1);
        QSort(vt,pivot+1,high);
    }
}
  1. 简单选择排序:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换,每趟选出最小(或最大)的元素放到最前面。时间复杂度O(n^2),空间复杂度O(1):
void SelectSort(vector<int> &vt)
{
    for(int i=0;i<vt.size();i++)
    {
        for(int j=i+1;j<vt.size();j++)
        {
            if(vt[i]>vt[j]){
                int tp=vt[i];
                vt[i]=vt[j];
                vt[j]=tp;
            }
        }
    }
}
  1. 堆排序:n个元素的序列{k1,k2,…,kn}当且仅当满足{ki<=k2i ; ki<=k2i+1}或{ki>=k2i ; ki>=k2i+1}时,称之为堆。堆顶元素必定是序列中的最大值(最小值)。若在输出堆顶的最值之后,调整堆似得剩余n-1个元素的序列重又构成堆,则得到次最值,以此类推便可以得到一个有序序列,即堆排序。时间复杂度O(nlogn),空间复杂度O(1):
void HeapAdjust(vector<int> &vt,int s,int m)
{
    if(s==m)return;//s==m==0的时候直接退出
    int tp=vt[s];
    for(int j=2*s+1;j<=m;j*=2)//依次往下调整堆
    {
        if(j<m&&vt[j]<vt[j+1]) ++j;
        if(tp>vt[j])break;
        vt[s]=vt[j];
        s=j;
    }
    vt[s]=tp;
}

void HeapSort(vector<int> &vt)
{
    for(int i=vt.size()/2-1;i>=0;--i) //从最后一个非叶子结点开始调整
        HeapAdjust(vt,i,vt.size()-1); //建立大顶堆
    for(int i=vt.size()-1;i>0;--i)
    {
        swap(vt[0],vt[i]);//将堆顶记录和最后一个元素交换,然后重新调整大顶堆
        HeapAdjust(vt,0,i-1);    
}
}
  1. 归并排序:利用有序表归并操作实现的排序,核心操作是将一位数组中前后相邻的两个有序序列归并为一个有序序列。时间复杂度O(nlogn),空间复杂度O(n):
void Merge(vector<int> ovt,vector<int> &vt,int f,int m,int b)
{
    int i,j;
    for(i=f,j=m+1;f<=m&&j<=b;++i)
    {
        if(ovt[f]<ovt[j])vt[i]=ovt[f++];
        else vt[i]=ovt[j++];
    }
    while(f<=m)vt[i++]=ovt[f++];
    while(j<=b)vt[i++]=ovt[j++];
}

void MSort(vector<int> &vt,int f,int b)
{
    if(f==b)vt[f]=vt[f];
    else{
        int m=f+(b-f)/2;
        MSort(vt,f,m);
        MSort(vt,m+1,b);
        Merge(vt,vt,f,m,b); //归并操作
    }
}

9.基数排序:借助“分配”和“收集”两种操作对单逻辑关键字进行排序的一种内部排序方法。时间复杂度O(d(n+rd)),空间复杂度O(rd)。

 

猜你喜欢

转载自blog.csdn.net/qq_35503380/article/details/80567793