2022年889计算机技术专业基础综合整理

2022年889计算机技术专业基础综合

考试内容:

计算机技术专业基础综合主要考查考生以下几个方面:

  1. 掌握数据结构的基本概念、基本原理和基本方法。

  2. 掌握数据的逻辑结构、存储结构及基本操作的实现,能够对算法进行时间复杂度与空间复杂度的分析。

  3. 能够运用数据结构的基本原理和方法进行具体问题的分析与求解,具备采用C语言设计与实现算法的能力。

题型和分值

选择题40分、填空题40分、问答题40分、算法题30分

参考书目

严蔚敏,吴伟民,数据结构(C语言版),清华大学出版社

应掌握的具体内容为:

一、基本概念

1.1 数据、数据项、数据元素、数据对象、数据结构等概念

数据(data)

  • 是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称
  • 是计算机程序加工的“原料”。

包括一切的图像、声音等可以通过编码而归之于数据的信息。

数据项(data item)

  • 是数据的不可分割的最小单位

诸如学生的学号、姓名等信息

数据元素(data element)

  • 是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。

诸如学生、教师等就是人的数据元素,但它不是最小的单位,比如学生还可以有学号、姓名等数据项。

数据对象(data object)

  • 是性质相同的数据元素的集合,是数据的一个子集。

例如,整数数据对象为:{0,±1,±2,…},字母字符数据对象为:{‘A’,’B’,…,’Z’}。

数据结构(data structure)

  • 相互之间存在一种或多种特定关系的数据元素的集合
  • 数据元素之间的关系称为结构(structure)
    • 集合:同属一个集合
    • 线性结构:一对一
    • 树形结构:一对多
    • 图状结构或网状结构:多对多
  • 形式定义:二元组 (D,S)
    • D是数据元素的有限集
    • S是D上关系的有限集

数据结构 = 数据集 + 关系 + 操作

抽象数据类型

  • 三元组:(D,S,P)
    • D:数据对象
    • S:D的关系集合
    • P:对D的基本操作
    • 主要目的:可以分析逻辑特性而不指定实现细节

1.2 数据的逻辑结构和存储结构

逻辑结构

  • 数据元素之间的关系

  • 分类

    • 线性结构:一对一
    • 非线性结构
      • 集合
      • 树形结构
      • 图形结构

存储结构

  • 数据结构在计算机中的表示(又称映像)称为数据的物理结构,又称为存储结构。
  • 包括
    • 顺序存储:数据元素顺序存放,每个存储结点只含一个元素。存储位置反映数据元素间的逻辑关系。存储密度大,但有些操作 (如插入、删除) 效率较差。
    • 链式存储:每个存储结点除包含数据元素信息外还包括一组 (至少一个) 指针。指针反映数据元素间的逻辑关系。这种方式不要求存储空间连续,便于动态操作 (如插入、删除等) ,但存储空间开销大 (用于指针) ,另外不能折半查找等。
    • 索引存储:除数据元素存储在一地址连续的内存空间外,尚需建立一个索引表,索引表中索引指示存储结点的存储位置 (下标) 或存储区间端点 (下标) ,兼有静态和动态特性。
    • 散列存储:通过散列函数和解决冲突的方法,将关键字散列在连续的有限的地址空间内,并将散列函数的值解释成关键字所在元素的存储地址,这种存储方式称为散列存储。其特点是存取速度快,只能按关键字随机存取,不能顺序存取。

1.3 算法时间复杂度分析和空间复杂度分析

时间复杂度

  • 一般情况下,算法中基本操作重复执行的次数是问题规模n的4某个函数 f ( n ) f(n) f(n),算法的时间度量记作 T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n)),它表示随问题规模n的增大,算法执行时间的增长率和 f ( n ) f(n) f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间复杂度。

其实就是基本语句的执行次数

空间复杂度

  • 算法所需存储空间的度量,记作 S ( n ) = O ( f ( n ) ) S(n)=O(f(n)) S(n)=O(f(n)),其中n为问题规模。

其实就是算法中用到的存储空间

1.4 迭代算法和递归算法的时间复杂度分析

迭代算法的时间复杂度 = 迭代次数 * 每次迭代时的操作次数

递归算法的时间复杂度 = 递归次数 * 每次递归中的操作次数

二、线性表

2.1 线性表的定义和基本操作

定义:一个线性表是n个数据元素的有限序列

基本操作

  • InitList(&L):初始化表。构造一个空的线性表
  • Length(L):求表长。返回线性表 L 的长度,即L中数据元素的个数
  • LocateElem(L,e):按值查找操作。在表 L 中查找具有给定关键字值的元素
  • GetElem(L,i):按位查找操作。获取表 L 中第 i 个位置的元素的值
  • ListInsert(&L,i,e):插入操作。在表 L 中的第 i 个位置上插入指定元素 e
  • ListDelete(&L,i,&e):删除操作。删除表 L 中第 i 个位置的元素,并用 e 返回删除元素的值
  • PrintList(L):输出操作。按前后顺序输出线性表 L 的所有元素值
  • Empty(L):判空操作。若 L 为空表,则返回 true,否则返回 false
  • DestroyList(&L):销毁操作。销毁线性表,并释放线性表 L 所占用的内存空间

2.2 顺序存储和链式存储

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

链式存储:用一组任意的存储单元存储线性表的数据元素。

  • 线性链表
  • 双向链表
  • 循环链表

2.3 线性表的应用

一元多项式的表示及相加

三、栈、队列和数组

3.1 栈和队列的基本概念

**栈 (stack)**是限定仅在表尾进行插入或删除操作的线性表

**队列 (queue)**是一种先进先出 (FIFO) 的线性表。它只允许在表的一端进行插入,而在另一端删除元素。

3.2 栈和队列的顺序存储结构

栈 P46

typedef struct
{
    SElemType *base;	// 存储空间初始分配量
    SElemType *top;		// 栈顶指针
    int stacksize;		// 当前已分配的存储空间,以元素为单位
}SpStack;

队列 P64

typedef struct
{
    QElemType *base;	// 初始化的动态分配存储空间
    int front;			// 头指针,若队列不空,指向队列头元素
    int rear;			// 尾指针,若队列不空,指向队尾元素的下一个位置
}SqQueue;

3.3 栈和队列的链式存储结构

栈 https://blog.csdn.net/qq_29542611/article/details/78899772

//链栈的数据结构
typedef struct LinkStack 
{
	LinkStackPoi top;	// 栈顶结点
	int count;			// 元素个数
}LinkStack;

队列 P61

// 单链队列
typedef struct QNode 
{
    QElemtype data;
    struct QNode *next;
}QNode, *QueuePtr;

typedef struct
{
    QueuePtr front;	//队头指针
    QueuePtr rear;	//队尾指针
}LinkQueue;

:A栈顶 → B → C → D栈底

队列:队头 → A → B → C → D队尾 (头出尾进)

3.4 栈和队列的基本操作

  • InitStack(&S):初始化一个空栈 S
  • StackEmpty(S):判断一个栈是否为空,若 S 为空则返回 true,否则返回 false
  • Push(&S,x):进栈,若栈 S 未满,则将 x 加入使之成为新栈顶
  • Pop(&S,&x):出栈,若栈 S 非空,则弹出栈顶元素,并用 x 返回栈顶元素
  • GetTop(S,&x):读栈顶元素,若栈 S 非空,则用 x 返回栈顶元素
  • DestroyStack(&S):销毁栈,并释放 S 占用的存储空间

队列

  • InitQueue(&Q):初始化队列,构造一个空队列 Q
  • QueueEmpty(Q):判队列空,若队列 Q 为空返回 true,否则返回 false
  • EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾
  • DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回
  • GetHead(Q,&x):读队头元素,若队列 Q 非空,则将队头元素赋值给 x

3.5 栈和队列的应用

  • 数制转换
  • 括号匹配检验
  • 行编辑程序
  • 迷宫求解
  • 表达式求值
  • 在程序设计语言中实现递归,如汉诺塔问题

队列

  • 离散事件模拟,如计算客户在银行平均逗留事件

3.6 特殊矩阵的压缩存储

特殊矩阵:指具有居多相同矩阵元素或零元素,并且这些相同矩阵或零元素的分布有一定规律性的矩阵

压缩矩阵:指为多个值相同的元素只分配一个存储空间,对零元素不分配存储空间

对称矩阵

  • a[i][j]=a[j][i]

  • 压缩方式:只存下三角区和对角元素

三角矩阵

  • 对于下三角矩阵,上三角区所有元素为同一常量

  • 压缩方式:只存下三角区和主对角元素,并在其后存储一个上三角区的常量

稀疏矩阵

  • 非零元素较少的矩阵

  • 压缩方式:三元组(行标,列标,值)

四、树和二叉树

4.1 树和二叉树的定义、性质及特征、存储结构

4.2 二叉树的遍历

// 后续非递归遍历 王道P141 T3
void PostOrder(BitreeNode T)
{
    Stack<BitreeNode> S;
    BitreeNode q,r;
    q = T;
    while(q || !S.isEmpty())
    {
        if(q)
        {
            S.push(q);
            q = q->lchild;
        }
        else
        {
            q = s.top();
            if(q->rchild && q->rchild!=r)
                q = q->rchild;
           	else
            {
                visit(p);
                S.pop();
                r=q;
                q=NULL;
            }
        }
    }
}

// 层序遍历
void LevelOrder(BitreeNode T)
{
    Queue<BitreeNode> Q;
    BitreeNode p;
    p = T;
    Q.push(P);
    while(!Q.isEmpty())
    {
        p = Q.top();
        Q.pop();
        visit(P);
        if(p->lchild)
            Q.push(p->lchild);
        if(p->rchild)
            Q.push(p->rchild);
    }
}

4.3 线索二叉树的基本概念和构造

若无左子树,令lchild指向其前驱结点;

若无右子树,令rchild指向其后继结点。

typedef struct ThreadNode
{
    ElemType data;
    struct ThreadNode *lchild, *rchild;
    int ltag,rtag;
    /*
    ltag=0,指向左孩子
    ltag=1,指向前驱
    rtag=0,指向右孩子
    rtag=1,指向后继
    */
}

4.4 二叉搜索树及其基本操作

4.5 平衡二叉树及其基本操作

平衡因子:结点左子树与右子树高度差

平衡二叉树:|平衡因子| ≤ 1

基本操作

  • 插入
    • LL平衡旋转
    • RR平衡旋转
    • LR平衡旋转
    • RL平衡旋转

k深度的平衡二叉树最小结点数

n 0 = 0 n_0 = 0 n0=0

n 1 = 1 n_1 = 1 n1=1

n 2 = 2 n_2 = 2 n2=2

n k = n k − 1 + n k − 2 + 1 n_k = n_{k-1} + n_{k-2} + 1 nk=nk1+nk2+1

4.6 哈夫曼树和哈夫曼编码

哈夫曼树

n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1

哈夫曼编码

4.7 树、森林与二叉树的转换

4.8 树和森林的遍历

五、图的基本概念和术语

5.1 图的概念

5.2 图的存储结构

邻接矩阵:

  • (i,j) = 0 表示 无边
  • (i,j) = 1 表示 有边

对于邻接矩阵A而言, A n A^n An 中 A[i,j] 的含义为从 i 到 j 长度为 n 的路径的数量

邻接表

  • 对于每个顶点都把其邻接的顶点串成一个链表
  • 对于有向图的改进:十字链表
  • 对于无向图的改进:邻接多重表

5.3 图的遍历

深度优先搜索(DFS)

广度优先搜索(BFS)

5.4 最小生成树、最短路径、拓扑排序和关键路径

最小生成树

  • Prim算法
    • 先选取一个顶点加入集合,然后每次选取离顶尖集合最近的顶点,将这个顶点和对应的边加入到集合中
  • Kruskal算法
    • 每次都选取权值最小的边,其顶点处于不同的连通分量中

本质上都是贪心算法

最短路径

  • 对于无权图:深度优先搜索

  • 对于有权图

    • 单源最短路径:Dijkstra算法

      1. dist赋初值为起点到其余顶点的权值
      2. 选取权值最小的顶点
      3. 从该顶点开始寻找它到其余顶点的最小权值,并更新权值
      4. 重复上述步骤23直到找到要求的终点的最短路径

      集合S:记录以求得的最短路径的顶点

      dist[]:记录从源点 v 0 v_0 v0到其他各顶点当前的最短路径

      path[]:path[i]表示从源点到顶点i之间的最短路径的前驱结点(可以追溯最短路径)

    • 每对顶点间最短路径:Floyd算法

      • 每一轮以 v m v_m vm为起点,看 A m − 1 [ i ] [ j ] A^{m-1}[i][j] Am1[i][j]的值是否大于 A m − 1 [ i ] [ m ] + A m − 1 [ m ] [ j ] A^{m-1}[i][m] + A^{m-1}[m][j] Am1[i][m]+Am1[m][j]的值,如果是则更新,更新后记为 A m [ i ] [ j ] A^m[i][j] Am[i][j]
      • m初始值为-1,经过顶点个数次迭代最终得到的每个 A [ i ] [ j ] A[i][j] A[i][j]都是最短路径

      A k [ i ] [ j ] A^k[i][j] Ak[i][j]代表第k次迭代从顶点i到顶点j的最短路径

拓扑排序:AOV网中每次选择一个没有前驱的顶点输出并删除该顶点和以它为起点的有向边

关键路径:

  • 最早开始时间:其余事件全部结束后该事件开始,所以是最长的路径
  • 最迟开始时间:从后往前看,后一个事件的最早开始时间-事件的时长

六、查找和排序

6.1 顺序查找、分块查找、折半查找

顺序查找

  • 哨兵的作用:避免很多不必要的判断语句,从而提高程序的效率

分块查找:

  • 块间有序,块内无序
  • 前一个块的最大关键字小于后一个块中任意关键字
  • 先用顺序或折半查找找到所在块,再在块内顺序查找
  • 最优块长:sqrt(n)

折半查找

  • 最大比较次数: l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)

6.2 B、B+树及其基本操作

B树

image-20211113155153445

B树

  • 插入
    • 通过“查找”确定插入位置(一定在终端结点)
    • 若插入后关键字个数未超过上限,无需其他处理
    • 若插入后关键字个数超过上限,则需要将当前结点的中间元素放到父节点中,当前结点分裂为两个部分,该操作会导致父节点关键字个数+1,若父节点关键字个数也超过了上限,则需要再向上分裂(根节点的分裂会导致B树高度+1)
  • 删除
    • 非终端结点关键字
      • 用其直接前驱或直接后继替代其位置,转化为对“终端结点”的删除
      • 直接前驱:当前关键字左边指针所指子树中“最右下”的元素
      • 直接后继:当前关键字右边指针所指子树中“最左下”的元素
    • 终端结点关键字
      • 删除后结点关键字个数未低于下限,无需任何处理
      • 低于下限
        • 右兄弟够借,则用当前结点的后继,后继的后继依次顶替空缺
        • 左兄弟够借,则用当前结点的前驱,前驱的前驱依次顶替空缺
        • 左右兄弟都不够借,则需要与父节点内的关键字、左右兄弟进行合并。合并后导致父节点关键字数量-1,可能需要继续合并

image-20211113162152831

区别m阶B树m阶B+树
类比二叉查找树的进化→m叉查找树分块查找的进化→多级分块查找
关键字与分叉n个关键字对应n+1个分叉(子树)n个关键字对应n个分叉
结点包含的信息所有结点中都包含记录的信息只有最下层叶子结点才包含记录的信息(可使树更矮)
查找方式不支持顺序查找。查找成功时,可能停在任何一层结点,查找速度“不稳定”支持顺序查找。查找成功或失败都会到达最下一层结点,查找速度“稳定”

相同点:

  • 除根节点外,最少$\lceil m/2 \rceil $个分叉(确保结点不要太“空”)
  • 任何一个结点的子树都要一样高(确保“绝对平衡”)

6.3 散列表

散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为Hash(key)=Addr

  • 直接定址法:H(key) = key 或 a*key+b
  • 除留余数法:H(key) = key%p
  • 数字分析法:选取数码分布均匀的若干位作为散列地址
  • 平法取中法:取关键字的平方值的中间几位作为散列地址

散列表:根据关键字而直接进行访问的数据结构。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系

冲突处理

  • 开放定址法 H i = ( H ( k e y ) + d i ) H_i = (H(key)+d_i)%m Hi=(H(key)+di)
    • 线性探测法: d i = 0 , 1 , 2 , 3 , . . . d_i=0,1,2,3,... di=0,1,2,3,...
    • 平方探测法: d i = 0 2 , 1 2 , 2 2 , . . . d_i=0^2,1^2,2^2,... di=02,12,22,...
    • 再散列法: d i = H a s h 2 ( k e y ) d_i=Hash_2(key) di=Hash2(key) H i = ( H ( k e y ) + i ∗ H a s h 2 ( k e y ) ) H_i=(H(key)+i*Hash_2(key))%m Hi=(H(key)+iHash2(key)) (i 是冲突次数)
    • 伪随机序列法: d i = d_i= di=伪随机数序列
  • 拉链法:冲突的关键字存为一个链表

装填因子 α \alpha α = 表中记录数n / 散列表长度m

6.4 字符串模式匹配

简单模式匹配

KMP算法

/*
模式串s 和 匹配串t 下标都从1开始
(实际从0开始)
*/
#include <iostream>
using namespace std;

/*
优化前的next数组
next[j] = 0;    // j==1;
next[j] = 最大相等前后缀长度+1; // 有前后缀时
next[j] = 1;    // 其他情况

T = "aaab";
next[1] = 0;
next[2] = 1;
next[3] = 2;
next[4] = 3; 
*/
void get_next(string T, int next[])
{
    int i=1, j=0;
    next[0] = 0;
    while(i<T.length())
    {
        if(j==0 || T[i-1]==T[j-1])
        {
            ++i;
            ++j;
            next[i-1] = j;    // next[j+1]==next[j]+1
        }
        else
            j = next[j-1];
    }
}

/*
优化后的next数组 nextval
从j=2开始,依次判断T[j]是否等于P[next[j]],
若相等则讲next[j]修正为next[next[j]],直至不等

T = "aaab";
nextval[1] = 0;
nextval[2] = 0;
nextval[3] = 0;
nextval[4] = 3;
*/
void get_nextval(string T, int nextval[])
{
    int i=1, j=0;
    nextval[0] = 0;
    while(i<T.length())
    {
        if(j==0 || T[i-1]==T[j-1])
        {
            ++i;
            ++j;
            if(T[i-1]==T[j-1])
                nextval[i-1] = j;
            else
                nextval[i-1] = nextval[j-1];
        }
        else
            j = nextval[j-1];
    }
}


int Index_KMP(string S, string T, int next[])
{
    int i=1, j=1;
    while(i<=S.length() && j<=T.length())
    {
        if(j==0 || S[i-1]==T[j-1])
        {
            ++i;
            ++j;
        }
        else
            j = next[j-1];
    }
    if(j>T.length())
        return i-T.length();
    else
        return 0;
}

int main()
{
    string S = "googlogooglegooglo";
    string T = "google";
    int next[T.length()+1];
    int nextval[T.length()+1];
    get_next(T,next);
    get_nextval(T,nextval);
    cout<<Index_KMP(S,T,next)<<endl;
    cout<<Index_KMP(S,T,nextval)<<endl;
    return 0;
}

6.5 各种查找算法的分析及应用

七、排序

7.1 排序的基本概念

7.2 插入排序

直接插入排序

  • 前面为已排序的序列,将后面的插入到前面中
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 稳定
void InsertSort(Elemtype A[], int n)
{
    int i,j;
    for(i=2; i<n; ++i)
    {
        if(A[i]<A[i-1])
        {
            A[0] = A[i];
            for(j=i-1; j>=1; --j)
            {
                if(A[j]>A[0])
                    A[j+1] = A[j];
            }
            A[j+1] = A[0];
        }
    }
}

折半插入排序

  • 先折半查找有序表确认插入位置再插入
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 稳定

7.3 起泡排序

起泡排序

  • 空间复杂度: O ( 1 ) O(1) O(1)

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)

  • 稳定

7.4 简单选择排序

简单选择排序

  • 选择 i 后面最小的一个元素和 i 元素交换
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 不稳定

7.5 希尔排序

希尔排序

  • 将相距为 d 的元素设为一个子表进行直接插入排序,d 初始为 n/2,之后 d i + 1 = d i / 2 d_{i+1} = d_i/2 di+1=di/2(向上向下取整都可),直到d==1
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 不稳定

7.6 快速排序

快排

  • 大的放左边,小的放右边
  • 空间复杂度: O ( l o g 2 n ) O(log_2 n) O(log2n)
  • 时间复杂度: O ( n l o g 2 n ) O(n log_2 n) O(nlog2n)
  • 不稳定
#include <iostream>
#include <vector>
using namespace std;

//快排
void quickSort(vector<int> &a, int start, int end)
{
    //以start为基准,小的排左边,大的排右边
    if (start < end)
    {
        int x = a[start], i = start, j = end;
        while (i < j)
        {
            while (a[j] > x && i < j)
                j--;
            a[i] = a[j];
            while (a[i] < x && i < j)
                i++;
            a[j] = a[i];
        }
        a[i] = x;
        quickSort(a, start, i - 1); //递归左边
        quickSort(a, i + 1, end);   //递归右边
    }
}

int main()
{
    vector<int> a = {3, 1, 4, 2, 6, 7, 12};
    quickSort(a, 0, a.size() - 1);
    for (int i = 0; i < a.size(); ++i)
        printf("%d ", a[i]);
    return 0;
}

7.7 堆排序

堆排序

  • 堆:L[i]>=L[2i] 且 L[i]>=L[2i+1] (小于也行)
  • 构造初始堆:先排成完全二叉树,然后调整
  • 调整:将如果双亲小于孩子,则和大的孩子交换,否则不变,逐层向上调整
  • 堆排序,不断输出最上层结点(将上层层结点和最后一个结点交换),将最后一个结点放在堆顶并调整推
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 时间复杂度: O ( n l o g 2 n ) O(n log_2 n) O(nlog2n)
  • 不稳定

增序排序则使用大顶堆

7.8 二路归并排序

归并排序

  • 将数组分为n/2个有序表,然后归并成n/2/2个有序表直到变成一个完整的有序表
  • 归并:对两个数组进行比较,小的先放入
  • 空间复杂度: O ( n ) O(n) O(n)
  • 时间复杂度: O ( n l o g 2 n ) O(n log_2 n) O(nlog2n)
  • 稳定
#include <iostream>
#include <vector>
using namespace std;

// 对数组a[start]~a[middle] 与 a[middle+1]~a[end]进行归并
void Merge(vector<int> &a, int start, int middle, int end)
{
    // i:左边数组起始下标 j:右边数组起始下标 k:b数组下标
    int i = start, j = middle + 1, k = start;
    vector<int> b(a.size());

    // 从小到大插入
    while (i <= middle && j <= end)
    {
        if (a[i] <= a[j])
        {
            b[k] = a[i];
            k++;
            i++;
        }
        else
        {
            b[k] = a[j];
            k++;
            j++;
        }
    }

    // 加入未插完的
    while (i <= middle)
    {
        b[k] = a[i];
        k++;
        i++;
    }
    while (j <= end)
    {
        b[k] = a[j];
        k++;
        j++;
    }

    // 将排好序的b赋值给a
    for (i = start; i <= end; ++i)
        a[i] = b[i];
}

// 归并排序
void mergeSort(vector<int> &a, int start, int end)
{
    if (start >= end)
        return;
    else
    {
        int middle = (start + end) / 2;
        mergeSort(a, start, middle);
        mergeSort(a, middle + 1, end);
        Merge(a, start, middle, end);
    }
}

int main()
{
    vector<int> a = {3, 1, 4, 2, 6, 7, 12};
    mergeSort(a, 0, a.size() - 1);
    for (int i = 0; i < a.size(); ++i)
        printf("%d ", a[i]);
    return 0;
}

7.9 基数排序

基数排序

  • 从每个数字的最低位开始,从大到小排序 (分配+收集),直到最高位。

    关键位权重:最高位最大,最低位最低

    分为最高位优先 (MSD) 和最低位优先 (LSD)

  • 空间复杂度: O ( r ) O(r) O(r)

    r:辅助存储空间(r个队列,r个队头指针和r个队尾指针)

  • 时间复杂度: O ( d ( n + r ) ) O(d(n+r)) O(d(n+r))

    d:分配和收集的趟数

    一趟分配: O ( n ) O(n) O(n)

    一趟收集: O ( r ) O(r) O(r)

  • 稳定

7.10 外部排序

外部排序指待排序文件较大,内存一次放不下,需存放在外存的文件的排序

为减少平衡归并中外存读写次数所采用的方法:增大归并路数和减少归并段个数

  • 败者树:增大归并路数时用来减少比较次数

    对r个初始归并段进行k路归并

    原先每个关键字要进行k-1次比较

    败者树只需要进行树高次比较,即 l o g k r log_k r logkr

  • 置换-选择排序:增大初始归并段长度来减少归并段个数

    生成的是长短不一的初试归并段

  • 最佳归并树:对长短不一的归并段进行顺序的确定来减少IO次数

    类似于哈夫曼树

    对n个初始归并段进行k路归并,补虚段的情况:(n-1)%(k-1) = u

    • u==0 不需要补0
    • u!=0 补k-1-u个虚段

7.11 各种内部排序算法的比较

算法种类时间复杂度(最好情况)时间复杂度(平均情况)时间复杂度(最坏情况)空间复杂度稳定性
直接插入排序 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
冒泡排序 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
简单选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定
希尔排序 O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( 1 ) O(1) O(1)不稳定
快速排序 O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( n 2 ) O(n^2) O(n2) O ( l o g 2 n ) O(log_2 n) O(log2n)不稳定
堆排序 O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( 1 ) O(1) O(1)不稳定
二路归并排序 O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( n l o g 2 n ) O(n log_2 n) O(nlog2n) O ( n ) O(n) O(n)稳定
基数排序 O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) O ( r ) O(r) O(r)稳定

7.12 排序算法的应用

规模不大 (n<10000):直接插入排序、冒泡排序、简单选择排序

中等规模 (n<=1000):希尔排序

大规模:快排、堆排序、归并排序、基数排序

混合使用

大规模数据求前n个有序数组:堆排序

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值