欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据结构复习

程序员文章站 2022-03-27 10:23:21
...

目录

第一章 绪论

第二章 线性表

顺序表

单链表

循环链表

第三章 栈和队列

队列

矩阵的压缩存储

第四章 树与二叉树

 二叉树

线索二叉树

树、森林

树和二叉树的应用

平衡二叉树

哈夫曼树

第五章 图

图的存储和基本操作

 第六章 排序

直接插入排序

希尔排序

冒泡排序

快速排序

简单选择排序

归并排序


 第一章 绪论

1、数据元素之间的关系在计算机中有两种不同的表示方法:顺序映像和非顺序映像,并由此得到两种不同的存储结构:顺序存储结构和链式存储结构。

2、抽象数据类型(Abstract Data Type, ADT)是指一个数学模型以及定义在该模型上的一组操作。ADT的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关,即不论内部结构如何变化,只要它的数学属性不变,都不影响其外部使用。

ADT 抽象数据类型名{

    数据对象:<数据对象的定义>

    数据关系:<数据关系的定义>

    基本操作:<基本操作的定义>

}ADT抽象数据类型名

3、算法特性

有穷性、确定性(无二义性)、可行性、输入、输出

4、算法设计要求:

正确性、可读性、健壮性、效率与低存储量需求

5、复杂度

O(1)<O(log2n)<O(n)<O(nlog2n)<O(n*n)<O(n*n*n)<O(2^n)

第二章 线性表

1、线性表是n个数据元素的有限集合

2、typedef与define的区别:摘自

typedef:可以按照自己的习惯定义数据类型名称

define:#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。eg,#define PI 3.1415926

(1)原理不同

#define是C语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。

typedef是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用typedef。用typedef定义数组、指针、结构等类型会带来很大的方便,不仅使程序书写简单,也使意义明确,增强可读性。

(2)功能不同

typedef用来定义类型的别名,起到类型易于记忆的功能。另一个功能是定义机器无关的类型。如定义一个REAL的浮点类型,在目标机器上它可以获得最高的精度:typedef long double REAL, 在不支持long double的机器上,看起来是这样的,typedef double REAL,在不支持double的机器上,是这样的,typedef float REAL

#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

(3)作用域不同

#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域。

3、new与malloc的区别:摘自

(1)属性

new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

(2)参数

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

(3)返回类型

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

(4)分配失败

new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

(5) 自定义类型

new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

(6)重载

C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。

(7)内存区域

new操作符从*存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。*存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为*存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。*存储区不等于堆,如上所述,布局new就可以不位于堆中。

顺序表

1、定义:用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。

2、特点:

(1)随机访问,O(1)时间找到制定元素

(2)存储密度高,每个节点只存储数据元素

(3)顺序表逻辑上相邻的元素物理上也相邻,插入删除需要移动。

//顺序表的构建、插入、删除、查找

#include<iostream>
#include<cstdio>
#include<malloc.h>

using namespace std;
#define List_size 10
#define maxL 50
typedef struct LNode{
    int *data;
    int len;
}List;

void init(List &L){
    L.data=(int*)malloc(List_size*sizeof(int));
    for(int i=0;i<10;i++){
        L.data[i]=i+1;
    }
    L.len=10;
}

bool Insert(List &L,int pos,int e){//在第pos个位置出插入e
    if(pos<1||pos>L.len+1){
        return false;
    }
    if(L.len>=maxL){
        return false;
    }
    for(int i=L.len;i>=pos;i--){
        L.data[i]=L.data[i-1];
    }
    L.data[pos-1]=e;
    L.len++;
    return true;
}

bool Delete(List &L,int pos,int &e){//删除pos位置上的数,并赋给e
    if(pos<1||pos>L.len){
        return false;
    }
    e=L.data[pos-1];
    for(int i=pos;i<L.len;i++){
        L.data[i-1]=L.data[i];
    }
    L.len--;
    return true;
}

int Locate(List L,int e){
    for(int i=0;i<L.len;i++){
        if(L.data[i]==e){
            return i+1;
        }
    }
    return 0;
}

void print(List L){
    for(int i=0;i<L.len;i++){
        printf("%d ",L.data[i]);
    }
    printf("\n");
}
int main(){
    List L;
    init(L);
    print(L);
    //插入
    Insert(L,3,8);
    print(L);
    //删除
    int e;
    Delete(L,3,e);
    printf("删除了%d\n",e);
    print(L);
    //查找
    int k=Locate(L,3);
    printf("第3个位置上的元素是:%d\n",k);
}

数据结构复习

单链表

单链表(线性表的链式存储):

插入删除不需要移动元素,只需要修改指针

是非随机存取的存储结构,查找节点时要遍历

//单链表的构建、插入、删除、查找

#include<iostream>
#include<cstdio>
#include<malloc.h>

using namespace std;
#define List_size 10
#define maxL 50
typedef struct LNode{
    int data;
    struct LNode *next;
}LNode,*List;

void init(List &L){//尾插
    L=(List)malloc(sizeof(LNode));
    LNode *s,*r=L;
    for(int i=1;i<=10;i++){
        s=(LNode*)malloc(sizeof(LNode));
        s->data=i;
        r->next=s;
        r=s;
    }
    r->next=NULL;
}


LNode *GetElem(List L,int i){//找第i个位置上的节点
    int j=1;
    LNode *p=L->next;
    if(i==0)return L;
    if(i<1)return NULL;
    while(p&&j<i){
        p=p->next;
        j++;
    }
    return p;
}

void Insert(List &L,int i,int e){//在第i个位置插入元素e
    LNode *p=GetElem(L,i-1);
    LNode *s=(LNode*)malloc(sizeof(LNode));
    s->data=e;
    s->next=p->next;
    p->next=s;
}

void Delete(List &L,int i){//删除第i个位置上的元素
    LNode *p=GetElem(L,i-1);
    LNode *q=p->next;
    p->next=q->next;
    printf("删除了%d\n",q->data);
    free(q);
}

void print(List L){
    LNode *p=L->next;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next;
    }
    printf("\n");
}
int main(){
    List L;
    init(L);
    print(L);
    //插入
    Insert(L,3,8);
    print(L);
    //删除
    Delete(L,3);
    print(L);
    //查找
    LNode *p=GetElem(L,3);
    printf("第3个位置上的元素是:%d\n",p->data);
}

数据结构复习

循环链表

//循环链表的构建、插入、删除、查找

#include<iostream>
#include<cstdio>
#include<malloc.h>

using namespace std;
#define List_size 10
#define maxL 50
typedef struct LNode{
    int data;
    struct LNode *next,*pre;
}LNode,*List;

void init(List &L){//尾插
    L=(List)malloc(sizeof(LNode));
    LNode *s,*r=L;
    for(int i=1;i<=10;i++){
        s=(LNode*)malloc(sizeof(LNode));
        s->data=i;
        r->next=s;
        s->pre=r;
        r=s;
    }
    r->next=NULL;
}


LNode *GetElem(List L,int i){//找第i个位置上的节点
    int j=1;
    LNode *p=L->next;
    if(i==0)return L;
    if(i<1)return NULL;
    while(p&&j<i){
        p=p->next;
        j++;
    }
    return p;
}

void Insert(List &L,int i,int e){//在第i个位置插入元素e
    LNode *p=GetElem(L,i-1);
    LNode *s=(LNode*)malloc(sizeof(LNode));
    s->data=e;
    s->next=p->next;
    p->next->pre=s;
    p->next=s;
    s->pre=p;
}

void Delete(List &L,int i){//删除第i个位置上的元素
    LNode *p=GetElem(L,i-1);
    LNode *q=p->next;
    p->next=q->next;
    q->next->pre=p;
    printf("删除了%d\n",q->data);
    free(q);
}

void print(List L){
    LNode *p=L->next;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next;
    }
    printf("\n");
}
int main(){
    List L;
    init(L);
    print(L);
    //插入
    Insert(L,3,8);
    print(L);
    //删除
    Delete(L,3);
    print(L);
    //查找
    LNode *p=GetElem(L,3);
    printf("第3个位置上的元素是:%d\n",p->data);
    printf("第2个位置的元素是:%d\n",p->pre->data);

}

数据结构复习

顺序表和链表的比较:

(1)存取方式

顺序表可以顺序存取,也可以随机存取。链表只能从表头存取元素

(2)逻辑结构与物理结构

顺序表:逻辑上相邻的元素,其对应物理存储位置也相邻。

链表:逻辑上相邻的元素,其对应物理存储位置不一定相邻。

(3)查找、插入、删除操作

 

链表

顺序表

按值查找

O(n)

O(n)(有序的话二分O(log2n))

按序号查找

O(n)

O(1)

(4)空间分配

顺序存储:

              静态存储:满则不能装,要预先分配足够大内存

              动态存储:存储空间可扩充,但需要移动大量元素,效率低,且内存无足够大的连续存储空间会导致分配失败

链式存储:

              需要时分配,有内存空间就可分配,操作灵活高效。

第三章 栈和队列

栈:只允许在一端(栈顶)进行插入或删除操作的线性表

//顺序栈的基操

顺序栈的入栈操作收到数组上界的约束,当栈的最大空间不够时,可能出现上溢。

#include<iostream>
#include<cstdio>
#include<malloc.h>

using namespace std;
#define List_size 10
#define maxL 50
typedef struct{
    int data[maxL];
    int top;
}sta;

void initStack(sta &S){
    S.top=-1;
}
bool Push(sta &S,int i){
    if(S.top==maxL-1){
        return false;
    }
    S.data[++S.top]=i;
    return true;
}
bool getTop(sta &S){
    if(S.top==-1)return false;
    int x=S.data[S.top];
    printf("栈顶元素是:%d\n",x);
    return true;
}
bool Pop(sta &S){
    printf("出栈:\n");
    if(S.top==-1)return false;
    while(S.top!=-1){
        int x=S.data[S.top--];
        printf("%d ",x);
    }
    return true;
}
int main(){
    sta S;
    initStack(S);
    for(int i=1;i<=10;i++){//进栈
        Push(S,i);
    }
    getTop(S);
    Pop(S);

}

数据结构复习

//链栈的基操

链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常用单链表实现,并规定所有操作都是在单链表的表头进行(头插法)

注意:

链栈没有头指针,S指针指向栈顶元素,S初始化为NULL,最后栈底元素的next是NULL

#include<iostream>
#include<cstdio>
#include<malloc.h>

using namespace std;
#define List_size 10
#define maxL 50

typedef struct LNode{
    int data;
    struct LNode *next;
}LNode,*sta;

void initStack(sta &S){
    S=NULL;
}

void Push(sta &S,int e){//无头结点
    LNode *p;
    p=(LNode*)malloc(sizeof(LNode));
    p->data=e;
    p->next=S;
    S=p;
}

bool getTop(sta &S){
    printf("栈顶元素是:%d\n",S->data);
}

bool Pop(sta &S){
    printf("出栈:\n");
    LNode *p=S;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next;
    }
    printf("\n");
}
int main(){
    sta S;
    initStack(S);
    for(int i=1;i<=10;i++){//进栈
        Push(S,i);
    }
    getTop(S);
    Pop(S);
}

数据结构复习

栈的应用

括号匹配

表达式求值

队列

队列:也是一种操作受限的线性表,在表的一端进行插入(队尾),在另一端进行删除(队头)

当Q.rear==MaxSize的时候,不能说明队列已满,因为,会有出队的元素,这时候,队列中依然可以存放元素,但却出现了“上溢出”,这是一种“假溢出”

==》解决方法:循环队列

初始化:Q.front=Q.rear=0

出   队:Q.front=(Q.front+1)%MaxSize

入   队:Q.rear=(Q.rear+1)%MaxSize

队   长:(Q.rear+MaxSize-Q.front)%MaxSize

区分满或空:

(1)牺牲一个单元:

队满:(Q.rear+1)%MaxSize=Q.front

队空:Q.front==Q.rear

//顺序队列顺序存储基操(牺牲一个单元)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<stack>
using namespace std;
#define MaxSize 10
typedef struct{
    int data[MaxSize];
    int f,r;
}Q;

void init(Q &q){
    q.f=q.r=0;
}

void EnQ(Q &q,int e){
    if((q.r+1)%MaxSize==q.f){
        printf("the queue is full, and %d hasn't been inserted to it.\n",e);
        return ;
    }
    q.data[q.r]=e;
    q.r=(q.r+1)%MaxSize;
    printf("%d is successfully inserted to the queue!\n",e);
}

void DeQ(Q &q){
    if(q.r==q.f){
        printf("the queue is empty, you cannot delete an element!\n");
        return ;
    }
    printf("%d is removed.\n",q.data[q.f]);
    q.f=(q.f+1)%MaxSize;
}

void print(Q q){
    if(q.f==q.r){
        printf("Empty!\n");
    }
    else {
        int p=q.f;
        while(p!=q.r){
            printf("%d ",q.data[p]);
            p=(p+1)%MaxSize;
        }
        printf("\n");
    }
}

int main(){
    Q q;
    init(q);
    for(int i=1;i<=10;i++){
        EnQ(q,i);
    }
    print(q);
    for(int i=1;i<=5;i++){
        DeQ(q);
    }
    print(q);
    for(int i=1;i<=3;i++){
        EnQ(q,i);
    }
    print(q);
}

数据结构复习

//队列的链式存储基操

数据结构复习

不带头结点的链式队列操作比较麻烦,因此通常将链式队列设计成一个带头结点的单链表,这样插入和删除操作就统一了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<stack>
using namespace std;

typedef struct LNode{//队列节点
    int data;
    struct LNode *next;
}LNode;

typedef struct{//队列
    LNode *f,*r;
}LinkQ;

void init(LinkQ &q){
    q.f=q.r=(LNode*)malloc(sizeof(LNode));
    q.f->next=NULL;
}

void EnQ(LinkQ &q,int e){
    if(q.f==q.r){
        init(q);
    }
    LNode *s=(LNode*)malloc(sizeof(LNode));
    s->data=e;
    s->next=NULL;
    q.r->next=s;
    q.r=s;
}

void DeQ(LinkQ &q){
    if(q.r==q.f){
        printf("cannot dequeue!\n");
        return ;
    }
    LNode *p=q.f->next;
    q.f->next=p->next;
    if(q.r==p){//队列中只有一个元素
        q.r=q.f;
    }
    free(p);
}

void print(LinkQ q){
    if(q.r==q.f){
        printf("Empty!\n");
        return ;
    }
    LNode *p=q.f->next;
    while(1){
        printf("%d ",p->data);
        if(p==q.r)break;
        p=p->next;
    }
    printf("\n");
}

int main(){
    LinkQ q;
    init(q);
    for(int i=1;i<=10;i++){
        EnQ(q,i);
    }
    print(q);
    for(int i=1;i<=11;i++){
        DeQ(q);
    }
    print(q);
    for(int i=1;i<=3;i++){
        EnQ(q,i);
    }
    print(q);
}

数据结构复习

矩阵的压缩存储

1、压缩存储:指为多个值相同的元素只分配一个存储空间,对零元素不分配存储空间。其目的是为了节省存储空间。

2、特殊矩阵:指具有许多相同矩阵元素或零元素,并且这些相同矩阵元素或零元素的分布有一定规律性的矩阵。eg、对称矩阵、上(下)三角矩阵,对角矩阵等。

3、特殊矩阵压缩存储方法:找到特殊矩阵中值相同的矩阵元素的分布规律,把那些呈现规律性分布的、值相同的多个矩阵压缩存储到一个存储空间。

对称矩阵(沿矩阵对角线对称)

把A[1...n][1...n]存到B[n(1+n)/2]里,从0开始存

Aij在B中对应的下标k为:

k=i*(i-1)/2+j-1    (i>=j)

k=j*(j-1)/2+i-1   (i<j)

三角矩阵

(1)下三角矩阵:上三角区所有的元素均为同一常量

把A[1...n][1...n]存到B[n(1+n)/2+1]里,从0开始存

Aij在B中对应的下标k为:

k=i*(i-1)/2+j-1    (i>=j)

k=n*(1+n)/2  (i<j)

(2)上三角矩阵:下三角区所有的元素均为同一常量

把A[1...n][1...n]存到B[n(1+n)/2+1]里,从0开始存

Aij在B中对应的下标k为:

k=(n-i+2+n)*(n-(n-i+2)+1)/2+j-i+1-1=(2n-i+2)*(i-1)/2+(j-i)    (i<=j)

k=n*(1+n)/2  (i>j)

(3)三对角矩阵

对角矩阵也称带状矩阵。对于n阶方阵A中的任一元素aij,当|i-j|>1时,有aij=0(1<=i,j<=n),则称为三对角矩阵。在三对角矩阵中,所有非零元素都集中在以主对角线为中心的3条对角线区域,其他区域的元素都为零。

数据结构复习

把A[1...n][1...n]存到B[]里,从0开始存

则aij对应在B中的元素下标是:k=3*(i-1)-1+(j-i+2)-1=2i+j-3

反之,知道k,可推:i=floor((k+1)/3+1)   ;   j=k-2*i+3

(4)稀疏矩阵

矩阵元素个数s相对于矩阵中非零元素的个数t来说非常多,即s>>t的矩阵成为稀疏矩阵。

通常非零元素的行列分布还没有规律,所以用(i,j,v)这样一个三元组来存储

很显然,稀疏矩阵压缩存储后便时期了随机存取特性。

第四章 树与二叉树

1、树的定义:

树是n(n>=0)个节点的有限集合,n=0时,是空树。任意一棵非空树中应满足:

(1)有且仅有一个特定的成为根的结点。
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集合T1,T2…Tm,其中每个集合本身又是一棵树,并称为根结点的子树。

(树中的某个结点(除根结点外)最多只和上一层的一个结点(父结点)有直接关系,根节点没有直接上层结点,因此在n个结点中的树,有n-1条边,而树中的每个结点与其下一层的零个或多个结点(子结点)有直接关系)

2、基本术语

(1)树中一个结点的子结点个数称为该结点的度,树中结点最大度数称为数的度。

(2)结点的层次:根为1,向下+1

         结点的深度:根开始自顶向下累加 

         结点的高度:从叶子结点开始向上累加

         树的高度:树中结点的最大层数

(3)有序树:结点的子结点从左到右的顺序出现是有关联的。反之,无序树

(4)路径和路径长度:两个结点的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边数。

注意:由于树中的分支是有向的,即从双亲结点指向孩子结点,所以树中的路径是从上到小的,同一双亲结点的孩子结点之间不存在路径。

(5)森林:是m(m>=0)棵互不相交的树的集合。

3、树的性质

(1)树中结点数等于所有结点的度数+1

(2)度为m的树中第i层上至多有m^(i-1)个结点

假设,1到i-1层的每个结点的度都为m,则1,m,m*m,m*m*m……m^(i-1)

(3)高度为h的m叉树至多有(m^h-1)/(m-1)个结点

1,m,m*m,m*m*m……m^(h-1)

1*(1-m^h)/(1-m)=(m^h-1)/(m-1)

(4)具有n个结点的m叉树的最小高度为数据结构复习

1,m,m*m,m*m*m……m^(h-1)

1*(1-m^h)/(1-m)=(m^h-1)/(m-1)=n

m^h=(m-1)*n+1

h=数据结构复习

 二叉树

1、定义:二叉树的特点是每个结点至多只有两棵子树,并且二叉树的子树有左右之分,其次序不能任意颠倒。

(即使二叉树一个结点只有一个孩子结点也要区分左右)

2、特殊的二叉树

(1)满二叉树:高度为h,有2^h-1个结点

(2)完全二叉树:所有结点与相对应的满二叉树相对应(无空隙)

(3)二叉排序树:一棵二叉树或是空二叉树,或是具有如下性质的二叉树:左子树上所有结点的关键字均小于根节点的关键字,右子树上所有结点的关键字均大于根节点的关键字。左右子树又各是一棵二叉排序树

(4)平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1

3、二叉树的性质

(1)非空二叉树上的叶子结点数等于度为2的结点数+1,即n0=n2+1

二叉树结点的度可能为:0,1,2。

设度为0,1,2的节点个数有n0,n1,n2个,则节点总数n=n0+n1+n2

二叉树的边有:n-1条,n-1=n1+2*n2

即n0+n1+n2-1=n1+2*n2

n0=n2+1

(2)非空二叉树上第k层上至多有2^(k-1)个结点(k>=1)

(3)高度为h的二叉树至多有2^h-1个结点(h>=1)

(4)对完全二叉树从上到下、从左到右编号1,2,……,n则有下面关系:

a.当i>1时,双亲结点编号:floor(i/2)   左孩子:i*2(i*2<=n)   右孩子:i*2+1(i*2+1<=n)

b.结点i所在的层次(深度)为:数据结构复习

(5)具有n个(n>0)结点的完全二叉树高度为数据结构复习数据结构复习

4、二叉树的存储结构

(1)顺序存储

满二叉树和完全二叉树比较适用,反之要在空结点位置上存0,造成空间效率低下。

(2)链式存储

一般都采用链式存储。

在含有n个结点的二叉链表中,含有n+1个空链域

数据结构复习

n个结点,共2n个链域

n-1条边,每条边占2个域(1个链域,1个数据域)

所以2n-(n-1)=n+1

二叉树的遍历

数据结构复习

(1)先、中、后序的递归算法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<stack>
using namespace std;
typedef pair<char,int> P;

typedef struct BiNode{
    char data;
    struct BiNode *lc,*rc;
}BiNode,*BiTree;

void init(BiTree &T){
    char ch;
    scanf("%c",&ch);
    if(ch=='#')T=NULL;
    else{
        T=new BiNode;
        T->data=ch;
        init(T->lc);
        init(T->rc);
    }
}

void pre_O(BiTree T){
    if(T==NULL)return ;
    printf("%c",T->data);
    pre_O(T->lc);
    pre_O(T->rc);
}
void in_O(BiTree T){
    if(T==NULL)return ;
    in_O(T->lc);
    printf("%c",T->data);
    in_O(T->rc);
}
void post_O(BiTree T){
    if(T==NULL)return ;
    post_O(T->lc);
    post_O(T->rc);
    printf("%c",T->data);
}
int main(){
    BiTree T;
    init(T);
    printf("先序遍历为:\n");
    pre_O(T);
    printf("\n中序遍历为:\n");
    in_O(T);
    printf("\n后序遍历为:\n");
    post_O(T);
}
/*
ABC##DE#G##F###
*/

数据结构复习

(2)先、中、后序的非递归算法思路源自

  • 先序遍历:

因为每次都最先访问其根节点,所以根节点不用存储,只用栈存右结点

(用栈就是为了帮我们保存还没有被访问的地址,以便将来我们能找到返回的地址)。

  •  中序遍历:

扫描根节点将其入栈,然后一直入栈其左子结点,直到入栈了叶子结点(其左结点为空)

出栈一个结点,并访问,然后扫描其右子结点,将其入栈,再扫描其右子结点的左节点并入栈,如此继续,直到栈空。

  • 后序遍历:

对于一个结点,可以访问它的条件是:左右子树已经遍历完

所以要一个标志位tag,tag=0表示结点不能访问;tag=1表示结点可以访问

若这次返回是从左结点返回的,就不能访问根节点,反之,能。

(1)搜索到p结点时,先把其左子树结点全部入栈,(结点地址p和tag)

(2)出栈一个最靠下的左结点,然后访问它,再遍历其右结点,tag置为1

(3)当右结点遍历完毕后,便可对p结点访问。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<stack>
using namespace std;


typedef struct BiNode{
    char data;
    struct BiNode *lc,*rc;
}BiNode,*BiTree;

typedef pair<BiNode*,int> P;

void init(BiTree &T){
    char ch;
    scanf("%c",&ch);
    if(ch=='#')T=NULL;
    else{
        T=new BiNode;
        T->data=ch;
        init(T->lc);
        init(T->rc);
    }
}

void pre_O(BiTree T){
    stack<BiNode*>S;
    BiNode *p=T;
    S.push(p);
    while(S.size()){//当栈不为空时
        p=S.top();S.pop();
        while(p){
            printf("%c",p->data);
            if(p->rc){
                S.push(p->rc);//把右节点入栈
            }
            p=p->lc;
        }
    }
}
void in_O(BiTree T){
    stack<BiNode*>S;
    BiNode *p=T;
    S.push(p);
    while(S.size()){//当栈不为空时
        p=S.top();
        while(p){//把左结点都入栈
            S.push(p->lc);
            p=p->lc;
        }
        S.pop();//出栈一个空结点
        if(S.size()){
            p=S.top();S.pop();
            printf("%c",p->data);//访问
            S.push(p->rc);//把右节点入栈
        }
    }
}
void post_O(BiTree T){
    int tag;
    BiNode *p=T;
    P t;
    stack<P>S;
    while(p||(S.size())){
        while(p){
            t.first=p;
            t.second=0;
            S.push(t);
            p=p->lc;
        }
        t=S.top();S.pop();
        p=t.first;
        tag=t.second;
        if(tag==0){
            t.first=p;
            t.second=1;
            S.push(t);
            p=p->rc;
        }
        else{
            printf("%c",p->data);
            p=NULL;
        }
    }
}
int main(){
    BiTree T;
    init(T);
    printf("先序遍历为:\n");
    pre_O(T);
    printf("\n中序遍历为:\n");
    in_O(T);
    printf("\n后序遍历为:\n");
    post_O(T);
}
/*
ABC##DE#G##F###
*/

数据结构复习

(3)层序遍历:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
using namespace std;


typedef struct BiNode{
    char data;
    struct BiNode *lc,*rc;
}BiNode,*BiTree;

void init(BiTree &T){
    char ch;
    scanf("%c",&ch);
    if(ch=='#')T=NULL;
    else{
        T=new BiNode;
        T->data=ch;
        init(T->lc);
        init(T->rc);
    }
}
void ceng(BiTree T){
    queue<BiNode*>Q;
    BiNode *q=T,*t;
    Q.push(q);
    while(Q.size()){
        t=Q.front();Q.pop();
        if(t){
            printf("%c",t->data);
        }
        if(t->lc)Q.push(t->lc);
        if(t->rc)Q.push(t->rc);
    }
}

int main(){
    BiTree T;
    init(T);
    printf("层序遍历为:\n");
    ceng(T);
}
/*
ABC##DE#G##F###
*/
由遍历序列构造二叉树:

先、中=>唯一确定一棵二叉树:戳我(*╹▽╹*)

后、中=>唯一确定一棵二叉树:戳我(*╹▽╹*)

层、中=>唯一确定一棵二叉树:戳我(*╹▽╹*)

先、后不能唯一确定一棵二叉树

线索二叉树

传统的链式存储仅能一线一种父子关系,不能直接得到结点在遍历中的前驱和后继。通过观察我们发现二叉链表表示的二叉树中存在大量的空指针,若利用这些空链域存放指向其直接前驱或后继的指针,则可以更方便地运用某些二叉操作算法。引入线索二叉树是为了加快查找节点前驱和后继的速度

(传统的链式存储,比如中序遍历,递归遍历完左子树后,向上一层一层返回,到根节点,再遍历根节点,

用线索二叉树,可以直接返回到根节点)

前面提到过,在有n个节点的二叉树中,有n+1个空指针。

数据结构复习

(ps,记得初始化tag的值为0鸭,QwQ)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
using namespace std;


typedef struct TNode{
    char data;
    int rtag=0,ltag=0;
    struct TNode *lc,*rc;
}TNode,*ThreadTree;

void init(ThreadTree &T){
    char ch;
    scanf("%c",&ch);
    if(ch=='#')T=NULL;
    else{
        T=new TNode;
        T->data=ch;
        init(T->lc);
        init(T->rc);
    }
}

void InThread(ThreadTree &p,ThreadTree &pre){
    if(p){
        InThread(p->lc,pre);
        if(p->lc==NULL){
            p->lc=pre;
            p->ltag=1;
        }
        if(pre!=NULL&&pre->rc==NULL){
            pre->rc=p;
            pre->rtag=1;
        }
        pre=p;
        InThread(p->rc,pre);
    }
    else return ;
}

void CreateInThread(ThreadTree &T){
    ThreadTree pre=NULL;
    if(T){
        InThread(T,pre);
        pre->rc=NULL;
        pre->rtag=1;
    }
}

TNode *Firstnode(TNode *p){//求中序搜索二叉树中中序序列下的第一个结点
    while(p->ltag==0)p=p->lc;
    return p;
}

TNode *NextNode(TNode *p){//求中序搜索二叉树中结点p在中序序列下的后一个结点
    if(p->rtag==0){
        return Firstnode(p->rc);
    }
    else return p->rc;
}

void In_O(TNode *T){
    for(TNode *p=Firstnode(T);p!=NULL;p=NextNode(p)){
        printf("%c",p->data);
    }
    printf("\n");
}

int main(){
    ThreadTree T;
    init(T);
    CreateInThread(T);
    In_O(T);
}
/*
ABC##DE#G##F###
*/

数据结构复习

树、森林

树的存储结构:

(1)双亲表示法

利用一组连续空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲结点在数组中的位置。

数据结构复习

该存储利用了每个结点(除根节点)只有唯一双亲的性质,可以很快得到每个结点的双亲结点,但是求结点的子结点需要遍历整个结构。

#define MAX_TREE_SIZE 100
typedef struct{
    int data;
    int pra;
}PTNode;
typedef struct{
    PTNode nodes[MAX_TREE_SIZE]
    int n;//结点数
}PTree;

(2)孩子表示法

将每个结点的孩子结点都用单链表链接起来形成一个线性结构,此时n个结点就有n个孩子链表(叶子结点的孩子链表为空)

该存储可以很快得到每个结点的孩子结点,但是求结点的双亲结点需要遍历n个结点中孩子链表指针域所指向的n个孩子链表。

数据结构复习           数据结构复习

(3)孩子兄弟表示法(又称二叉树表示法)

即以二叉链表作为树的存储结构。孩子兄弟表示法使得每个结点包括3部分:结点值,指向结点第一个孩子结点的指针,指向结点下一个兄弟结点的指针(沿此域可以找到结点的所有兄弟结点)

 数据结构复习

typedef struct CSNode{
    int data;
    struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;

树、森林与二叉树的转换

树转换为二叉树的规则:

每个结点左指针指向它的第一个孩子结点,右指针指向它在树中的相邻兄弟结点,可表示为:“左孩子右兄弟”。由于根节点没兄弟结点,所以由树转换得到的二叉树没有右子树。

数据结构复习

森林转换为二叉树的规则:

把森林中每棵树转换为二叉树,再将第一棵树的根作为转换后二叉树的根,第二棵树作为其右子树,第三棵树作为第二棵树的右子树……依次类推

二叉树转换为森林的规则:

若二叉树非空,则二叉树的根及其左子树为第一棵树的二叉树形式,二叉树的根的右子树也看成是要转成森林的一棵二叉树,重复上述操作,直到最后产生一棵没有右子树的二叉树为止。

数据结构复习

树和森林的遍历:

树的遍历无中根遍历,因为一个结点的子结点可能有n(n>2)个

这里有一点需要注意的!表格里树的后根遍历等于二叉树的中序遍历,指的是:

树的后根遍历=该树转为二叉树后,对二叉树进行的中序遍历。

数据结构复习

树和二叉树的应用

二叉排序树(BST)

1、定义二叉排序树,也称二叉搜索树:

二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树:

(1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(3)它的左、右子树也分别为二叉搜索树。

2、删除操作(删除的是z结点)

(1)若z是叶子结点,直接删掉

(2)若z只有一棵左子树,或一棵右子树,删除后,把子树提上来

(3)若z有左、右子树

则找到z节点的右子树的最左的节点,把它替换删除结点。(若z结点右子树无左结点,则用其右子树根节点代替)

数据结构复习

3、二叉排序树查找效率

二叉排序树查找算法的平均查找长度取决于树的高度,即与二叉树的形态有关。若其为只有右(左)孩子的单支树,平均查找长度为O(n)。若二叉排序树左右子树的高度只差的绝对值不超过1,这样的二叉排序树称为平衡二叉树。平均查找长度达到O(log2n)

嘻嘻,然后来快乐的水两道题:

LeetCode450 删除二叉搜索树中的结点

hdu3791二叉树的建立遍历

平衡二叉树

平衡二叉树(Balancing Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

定义结点左子树右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是:-1,0,1

平衡二叉树插入删除

哈夫曼树

给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(WPL)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

构造:

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

(思路就是:把权值较大的点放在上面~)

哈弗曼编码:

(1)对于待处理的一个字符串序列, 若对每个字符用同样长度的二进制位表示,则称这种编码方式为固定长度编码。若允许对不同字符用不等长的二进制位表示,则这种方式称为可变长度编码。可变长度编码比固定长度编码好得多,其特点是对频率高的字符赋以短编码,而对频率低的字符赋以较长一些的编码,从而可以使字符平均长度减短,起到压缩数据的效果。哈夫曼编码是一种被广泛应用而且非常有效的数据压缩编码。

(2)若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码

eg、0、101、100是前缀编码,因为没有一个码是其他码的前缀。所以,可以识别出第一个编码,将它翻译为原码,再对余下的编码文件重复同样的解码操作。eg、00101100可被唯一地分析为0、0、101、100

由哈夫曼树得到哈夫曼编码是很自然的过程。首先,将每个出现的字符当作一个独立的节点,其权值为它出现的频度(次数),构造出哈弗曼树。显然,所有结点都出现在叶子结点。我们可以将字符的编码结束为从根至该字符的路径上边标记的序列,其中边标记为0表示“转向左孩子”,标记为1表示“转向右孩子”。

数据结构复习

第五章 图

1、简单图:满足

(1)不存在重复边

(2)不存在顶点到自身的边

2、多重图

若图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己相联,则G为多重图。多重图的定义和简单图是相对的。

3、完全图

在无向图中,若任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有n(n-1)/2条边。在有向图中,若任意两个顶点之间都存在方向相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有n(n-1)条有向边。

4、连通:若顶点v到w有路径存在,则成v,w是连通的

连通图:若图中任意两个顶点都是联通的,则称图为连通图。

强连通:有向图中,若从顶点v到w和从顶点w到v之间都有路径,则称这两个顶点是强连通的。

有向图中的极大强连通子图称为有向图的强连通分量。

5、稀疏图:|E|<|V|log|V|

图的存储和基本操作

图的存储

1.邻接矩阵法,用矩阵表示,a[i][j]=1有边

2.邻接表法

3.十字链表

裸最短路

dijk:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
using namespace std;
const int N=105;
const int INF=0x3f3f3f3f;
int cost[N][N];
int d[N],vis[N],n,m;

void dijk(){
    d[1]=0;
    for(int j=1;j<=n;j++){
        int v=-1;
        //每次选一个到源点最近的点
        for(int i=1;i<=n;i++){
            if(!vis[i]&&(v==-1||d[i]<d[v])){
                v=i;
            }
        }
        vis[v]=1;
        for(int i=1;i<=n;i++){
            if(d[i]>d[v]+cost[v][i]){
                d[i]=d[v]+cost[v][i];
            }
        }
    }
}

void init(){
    memset(d,INF,sizeof(d));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cost[i][j]=INF;
        }
    }
}

int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        if(m+n==0)break;
        init();
        int a,b,c;
        while(m--){
            scanf("%d%d%d",&a,&b,&c);
            if(cost[a][b]>c){
                cost[a][b]=c;
                cost[b][a]=c;
            }
        }
        dijk();
        printf("%d\n",d[n]);
    }
}

bellman:

检查是否存在负环:如果在进行过n-1次松弛操作后还存在可以松弛的边,那么说明有负环。

(如果没有负环的话松弛操作完后,d[i]就表示点i到原点最小的长度,还能再松弛说明有一个边是负的)

时间复杂度:O(VE),用队列优化复杂度为O(kE),k为每个节点入队次数,也就是SPFA算法。
 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#define ll long long
using namespace std;
const int N=105,M=10005;
const int INF=0x3f3f3f3f;
struct A{
    int from,to,cost,nex;
}edge[2*M];

int n,m,cnt=0,head[N],d[N];
void add(int from,int to,int cost){
    edge[cnt].from=from;
    edge[cnt].to=to;
    edge[cnt].cost=cost;
    edge[cnt].nex=head[from];
    head[from]=cnt++;
}

int bellman(){
    int flag;
    d[1]=0;
    for(int i=1;i<=n;i++){
        flag=0;
        for(int j=0;j<cnt;j++){
            if(d[edge[j].to]>d[edge[j].from]+edge[j].cost){
                d[edge[j].to]=d[edge[j].from]+edge[j].cost;
                flag=1;
            }
        }
        if(!flag)break;
    }
    for(int i=0;i<cnt;i++){//判负环
        if(d[edge[i].to]>d[edge[i].from]+edge[i].cost){
            return 1;
        }
    }
    return 0;
}
void init(){
    cnt=0;
    memset(head,-1,sizeof(head));
    memset(edge,0,sizeof(0));
    memset(d,INF,sizeof(d));
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        if(m+n==0)break;
        init();
        int a,b,c;
        while(m--){
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        bellman();
        printf("%d\n",d[n]);
    }
}

裸最小生成树

prim:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
using namespace std;
const int N=105;
const int INF=0x3f3f3f3f;
int cost[N][N];
int d[N],vis[N],n;

int prim(){
    for(int i=1;i<=n;i++){
        d[i]=INF;
        vis[i]=0;
    }
    d[1]=0;
    int res=0;
    for(int j=1;j<=n;j++){
        int v=-1;
        for(int i=1;i<=n;i++){
            if(!vis[i]&&(v==-1||d[v]>d[i]))v=i;
        }
        vis[v]=1;
        res+=d[v];
        for(int i=1;i<=n;i++){
            d[i]=min(d[i],cost[v][i]);
        }
    }
    return res;
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&cost[i][j]);
        }
    }
    int res=prim();
    printf("%d\n",res);
}

kruskal:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=105;
const int INF=0x3f3f3f3f;
int cost[N][N];
struct A{
    int from,to,cost;
}edge[N*N];
int n,cnt=0,pre[N];

void add(int from,int to,int cost){
    edge[cnt].from=from;
    edge[cnt].to=to;
    edge[cnt++].cost=cost;
}

int f(int x){
    if(x==pre[x])return x;
    pre[x]=f(pre[x]);
    return pre[x];
}

int cmp(A a,A b){
    return a.cost<b.cost;
}

int main(){
    scanf("%d",&n);
    int c;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&c);
            add(i,j,c);
        }
    }
    for(int i=1;i<=n;i++)pre[i]=i;
    sort(edge,edge+cnt,cmp);

    int ans=0,flag=0;
    for(int i=0;i<cnt;i++){
        int fx=f(edge[i].from);
        int fy=f(edge[i].to);
        if(fx!=fy){
            pre[fx]=fy;
            ans+=edge[i].cost;
            flag++;
        }
        if(flag==n-1)break;
    }
    printf("%d\n",ans);
}

 第六章 排序

排序分类:

内部排序:指待排序记录存放在计算机随机存储器中进行的排序过程。

外部排序:待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中需对外存进行访问的排序过程。

直接插入排序

思想:

有序序列L[1..i-1]把L[i]插入到其中,无序序列L[i+1]

(1)查找出L[i]在L[1..i-1]中的位置k

(2)L[k..i-1]中的元素全部后移

(3)L[i]复制到L[k]

时间复杂度:

(1)有序情况下:

进行n-1次比较,不移动。

(2)无序情况下:

比较次数:(n+2)(n-1)/2   即,数据结构复习

移动次数:(n+4)(n-1)/2   即,数据结构复习 

(3)总的来说时间复杂度是O(n^2)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=105;
int a[11]={0,10,9,8,7,6,5,4,3,2,1};

void insert_sort(int a[]){//a[0]空着不用
    for(int i=2;i<=10;i++){
        if(a[i]<a[i-1]){//与有序表中的最大的元素比较
            a[0]=a[i];
            int j;
            for(j=i-1;a[0]<a[j];j--){
                a[j+1]=a[j];
            }
            a[j+1]=a[0];
        }
    }
}

int main(){
    insert_sort(a);
    for(int i=1;i<=10;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
}

希尔排序

希尔排序(Shell's Sort),又称“缩小增量排序”,也是一种插入类排序方法,但是时间效率上有较大改进。

插入排序在数据规模小且基本有序时效率高。

希尔排序在数据规模大且无序时效率高。

学习博客

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=105;
int a[10]={10,9,8,7,6,5,4,3,2,1};
int n=10;

/*
将a[i]插到所在分组的正确位置上
*/
void insertI(int a[],int gap,int i){
    int inserted=a[i];
    int j=i-gap;
    for(;j>=0&&inserted<a[j];j-=gap){
        a[j+gap]=a[j];
    }
    a[j+gap]=inserted;
}
void Shell_sort(int a[]){
    int n=10;
    for(int gap=n/2;gap>0;gap/=2){
        for(int i=gap;i<n;i++){
            insertI(a,gap,i);
        }
    }
}

int main(){
    Shell_sort(a);
    for(int i=0;i<10;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
}

冒泡排序

思想:

假设表长为n,从后往前两两比较,若a[i-1]<a[i]则交换,没一趟冒泡,是把最小的元素交换到序列的正确的位置。

时间复杂度:

(1)有序情况下,进行n-1次比较,不移动。

(2)无序情况下,进行   数据结构复习  次比较和移动。

(3)总的来说时间复杂度是O(n^2)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=105;
int a[10]={10,9,8,7,6,5,4,3,2,1};
int n=10;
void bubble_sort(int a[]){
    for(int i=0;i<n-1;i++){//进行n-1次冒泡
        int flag=0;
        for(int j=n-1;j>i;j--){
            if(a[j-1]>a[j]){
                swap(a[j-1],a[j]);
                    flag=1;
            }
        }
        if(!flag)break;
    }
}

int main(){
    bubble_sort(a);
    for(int i=0;i<10;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
}

快速排序

是对冒泡排序的一种改进。

思想:

通过一趟排序将待排记录分成独立的两部分,其中一部分记录的关键字比另一部分记录的关键字小,这个分别对这两部分记录继续进行排序,以达整个序列有效。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=105;
int a[10]={10,9,8,7,6,5,4,3,2,1};
int n=10;

int Partition(int a[],int s,int e){
    int i=s,j=e+1;
    int x=a[s];
    while(1){
        while(a[++i]<x&&i<e);
        while(a[--j]>x);
        if(i>=j)break;
        swap(a[i],a[j]);
    }
    a[s]=a[j];
    a[j]=x;
    return j;
}

void quick_sort(int a[],int s,int e){
    if(s<e){
        int pos=Partition(a,s,e);
        quick_sort(a,s,pos-1);
        quick_sort(a,pos+1,e);
    }
}

int main(){
    quick_sort(a,0,9);
    for(int i=0;i<10;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
}

简单选择排序

思想:

第i趟选择L[i..n]中最小的元素和L[i]交换,每趟排序可以确定一个元素的最终位置。

一共是n-1趟就可以排好序。

时间复杂度:

记录移动的次数:最少为0,最大为3(n-1)

比较次数均为:n(n-1)/2

总的时间复杂度是:O(n^2)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=105;
int a[10]={10,9,8,7,6,5,4,3,2,1};
int n=10;
void select_sort(int a[]){
    for(int i=0;i<n-1;i++){//第i趟排序,选择[i,n]最小的元素,并与a[i]交换
        int pos=i;
        for(int j=i+1;j<n;j++){
            if(a[j]<a[pos]){
                pos=j;
            }
        }
        if(pos!=i)swap(a[i],a[pos]);
    }
}

int main(){
    select_sort(a);
    for(int i=0;i<10;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
}

归并排序

递归到终点时,是单个元素,然后向上,两两有序数组合并到temp数组,然后把temp数组的值,复制到原数组中。

此外在最坏、最佳、平均情况下归并排序时间复杂度均为o(nlogn).

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=105;
int a[10]={10,9,8,7,6,5,4,3,2,1};
int temp[10];
int n=10;


void Merge(int s[],int t[],int b,int m,int e){
    int i=b,j=m+1,k=0;
    while(i<=m&&j<=e){
        if(s[i]<=s[j])t[k++]=s[i++];
        else t[k++]=s[j++];
    }
    while(i<=m){
        t[k++]=s[i++];
    }
    while(j<=e){
        t[k++]=s[j++];
    }
    for(int i=0;i<k;i++){
        s[b+i]=t[i];
    }
}
int Msort(int s[],int t[],int b,int e){
    if(b<e){
        int m=(b+e)>>1;
        Msort(s,t,b,m);
        Msort(s,t,m+1,e);
        Merge(s,t,b,m,e);
    }
}

int main(){
    Msort(a,temp,0,9);
    for(int i=0;i<10;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
}

数据结构复习