数据结构——排序

数据结构笔记目录:

1. 绪论、时间复杂度
2. 线性表
3. 树
4. 图
5. 查找
6. 排序


6.1 基本概念

6.1.1 目标

使关键字有序

6.1.2 分类

内部排序

数据元素在排序期间全在内存中

外部排序

排序过程中,数据不断在内外存之间移动

6.1.3 稳定性

经过排序,关键字相同的元素保持原先的先后顺序

6.1.4 两种操作

比较

确定关键字先后关系

移动

移动元素,达到序列有序

6.1.5 一趟排序

对所有未处理的元素处理一遍

在这里插入图片描述

6.2 内部排序

6.2.1 总结归纳

稳定性

插冒归基 稳

  • 折半插入
  • 直接插入
  • 冒泡排序
  • 归并排序
  • 基数排序

是稳定的


不稳定排序算法有

  • 希尔排序
  • 快排
  • 简单选择排序
  • 堆排序

适用链式存储

插冒选归基

  • 直接插入排序
  • 冒泡排序
  • 简单选择排序
  • 归并排序
  • 基数排序

不会形成有序子序列

快排

时间复杂度

在这里插入图片描述

时间复杂度与初始状态无关

一堆()乌龟()选()基友()

  • 简单选择 O ( n 2 ) O(n^2) O(n2)
  • 堆排序 O ( n l o g n ) O(nlogn) O(nlogn)
  • 归并排序 O ( n l o g n ) O(nlogn) O(nlogn)
  • 基数排序 O ( r d ( n + r ) ) O(rd(n+r)) O(rd(n+r))
比较次数与初始状态无关

选择排序

与初始状态有关
插入排序
  • 最好:有序

    直接 O ( n ) O(n) O(n)

    折半 O ( n l o g n ) O(nlogn) O(nlogn)

  • 最坏:逆序

交换排序
  • 冒泡

    最好:有序 O ( n ) O(n) O(n)

    最坏:逆序 O ( n 2 ) O(n^2) O(n2)

  • 快排

    最好:枢轴中分 O ( n l o g n ) O(nlogn) O(nlogn)
    最坏:基本有序,逆序 O ( n 2 ) O(n^2) O(n2)

冒泡排序趟数与初始状态有关
为O(nlogn)
  • 快排
  • 堆排
  • 归并

空间复杂度

O(1)
  • 直接插入
  • 折半插入
  • 希尔排序
  • 冒泡排序
  • 简单选择
  • 堆排序
O(logn)

快排最好

O(n)
  • 快排最坏
  • 归并排序
  • 基数最坏 O ( n ) O(n) O(n)
    基数排序适用于数据元素个数 n ≫ r , d n\gg r,d nr,d 的情况,平均空间复杂度为 O ( r d ) O(rd) O(rd),所以一定小于 O ( n 2 ) O(n^2) O(n2)
    在该关键字 d i d_i di n n n 个元素取值都是 x t x_t xt ,此时需要 n n n 个队列空间,所以最坏情况,需要 O ( n ) O(n) O(n)

适用场景

规模

在这里插入图片描述

取第k小
  • k趟冒泡排序
  • k趟简单选择排序
  • k趟堆排序

形成部分有序序列

  • 直接插入
  • 冒泡排序
  • 快排
  • 简单选择
  • 堆排序

6.2.2 五大类排序

排序
插入排序
交换排序
选择排序
归并排序
基数排序
直接插入排序
折半插入排序
希尔排序
冒泡排序
快速排序
简单选择排序
堆排序

插入排序

思路

在这里插入图片描述

在最后一趟之前,所有元素都可能不在最终位置上

直接插入排序
思路

在这里插入图片描述

实现
//从小到大排序
void Direct_InsertSort(ElemType A[],int n){
	int i,j;
    for(i = 2;i <= n;++i){
        if(A[i] < A[i-1]){
            //若待插入元素A[i] < A[i-1] ,从后向前找插入位置
            A[0] = A[i];
            //A为一固定序列,且数据存储已定,所以需要哨兵记录待插入元素      
            for(j = i-1;A[0] > A[j];--j)
                A[j+1] = A[j];
           	A[j+1] = A[0];
        }
    }
}
特点

在这里插入图片描述

折半插入

直接插入查找插入位置时遍历改为折半插入

实现
void Binary_InsertSort(ElemType A[],int n){
    int i,j,low,high,mid;
    
    for(i = 2;i <= n;++i){//依次将A[2]~A[n]插入到有序子序列
        A[0] = A[i];
        
        low = 1;high = i - 1;
        while(low <= high){
            mid = (low+high)/2;
            if(A[mid] > A[0])
                high = mid - 1;
            else
                low = mid + 1;
        }
        //mid为插入位置
        for(j = i - 1;j >= mid;--j)
            A[j+1] = A[j];
        A[high + 1] = A[0];
    }
}
特点

在这里插入图片描述

希尔排序

理解:由于直接插入排序适合大致有序的序列,故可对数据进行预处理,再进行直接插入排序

思路
  • 每隔一个步长取一个数
  • 步长为两次取数之间的间隔数
特点

在这里插入图片描述

选择排序

思路:选最值,作为有序子序列的端点

简单选择排序
思路

在这里插入图片描述
在这里插入图片描述

实现
void selectSort(ElemType A[],int n){
    int i,j,min;
    for(i = 0;i < n-1;++i){
        minIndex = i;
        for(j = i + 1;j < n;++j)
            if(A[j] < A[minIndex])
                minIndex = j;
        if(minIndex != i)
            swap(A[minIndex],A[i]);
    }
    
}
特点

在这里插入图片描述

堆排序
思路

在这里插入图片描述

存储结构

在这里插入图片描述

步骤
  1. 建初始堆

    从最后一个非叶结点开始调整 n 2 或 n 2 + 1 ∼ 1 \frac{n}{2}或\frac{n}{2}+1 \sim 1 2n2n+11
    在这里插入图片描述

  2. 取根与堆的最后一个元素交换

  3. 重新调整堆,重复上述步骤

    在这里插入图片描述

操作

删除

  • 从上到下比较
  • 调用一次 HeapAdjust ,从根开始调整,两个结点先比,根据结果交换
  • 时间复杂度 O ( h ) = O ( l o g n ) O(h) = O(logn) O(h)=O(logn)

插入

  • 从下往上比

  • 比较范围:本子树

    K n + 1 K_{n+1} Kn+1 作为叶子结点插入末尾,重复将 K n + 1 K_{n+1} Kn+1 与其新双亲比较,直至 K n + 1 K_{n+1} Kn+1 满足堆的特点

实现
void BuildMaxHeap(ElemType A[],int n){
    for(i = n/2;i >= 1;--i)
        HeapAdjust(A,i,n);
}

void HeapAdjust(ElemType A[],int k,int n){
    A[0] = A[k];
    
    for(i = 2*k;i <= n;i*=2){//逐层向下调整,最值一定在左右孩子结点
        if(i < n && A[i] < A[i+1])//若右孩子结点比左孩子结点元素大
            i++;	//取右孩子结点下标
        if(A[0] >= A[i])
            break;	//将最大值放到根
        else{
            A[k] = A[i];
            l = i;//k为最值下标
        }
    }
    
    A[k] = A[0];
}

void HeapSort(ElemType A[],int n){
    BuildMaxHeap(A,n);
    for(i = n;i > 1;--i){
        swap(A[i],A[1]);
        HeapAdjust(A,1,i-2);
    }
}
特点

在这里插入图片描述

交换排序

冒泡排序
思路

每一趟都将一个最大或最小元素放在最终位置上
在这里插入图片描述
在这里插入图片描述

实现
void BubbleSort(int arr[],int n){
    for(int i = 0;i < n;++i){
        bool flag = false;//标记是否发生交换
        for(int j = 0;j > n-i-1;++j){//一趟冒泡排序
            if(arr[i] > arr[j+1]){//将大的放在后面
                swap(arr[j],arr[j+1]);
                flag = true;
            }
        }
        
        if(flag = false)//若某一趟未发生交换,说明已经有序
            return ;
    }
}
特点

在这里插入图片描述

快速排序
思路

冒泡排序的改进:分治法

不断移动枢轴位置,寻找平衡点

在这里插入图片描述

整体流程
在这里插入图片描述
一趟排序
在这里插入图片描述

实现
int Partition(ElemType A[],int low,int high){
    ElemType pivot = A[low];
    while(low < high){
        while(low < high && A[high] >= pivot)
            --high;
        A[low] = A[high];
        while(low < high && A[low] <= pivot)
            ++low;
        A[low] = A[high];
    }
    
    A[low] = pivot;
    
    return low;
}

void QuickSort(ElemType A[],int low,int high){
    if(low < high){
        int pivotpos = Partition(A,low,high);
        QuickSort(A,low,pivotpos-1);
        QuickSort(A,pivotpos+1,high);
    }
}
特点

在这里插入图片描述

归并排序

思路

分解

将含n个元素的带排序表分为 n 2 \frac{n}{2} 2n 元素的子表,分别对两个子表排序

合并

合并两个已排序的子表得到结果

在这里插入图片描述

实现
void Merge(ElemType A[],int low,int mid,int high){
    //表中 A[low,...,mid],A[mid+1,...,high]分别有序
    ElemType *B = (ElemType *)malloc(sizeof(ElemType)*(high-low+1));
    
    for(int i = 1;i <= high;++i)
        B[i] = A[i];//初始化辅助数组
    for(int i = low,j = mid+1,k = 1;i <= mid && j <= high;++k){
		if(B[i] <= B[j])
            A[k] = B[i++];
        else
            A[k] = B[j++];
    }
    
    while(i <= mid)
        A[k++] = B[i++];//第一个表未检测完,赋值
    while(j <= high)
        A[k++] = B[j++];//第二个表未检测完,赋值
}

void MergeSort(ElemTypeA[],int low,int high){
	if(low < high){
        int mid = (low + high)/2;
        MergeSort(A,low,mid);
        MergeSort(A,mid+1,high);
        Merge(A,low,mid,high);
    }
}
特点

在这里插入图片描述

基数排序

不是基于比较和查找

思路

每组关键字,按同一逻辑排序

先排主关键字,再排次关键字

先排主关键字,则主关键字排好后,无法处理次关键字相同的情况,实际情况 先排次关键字,再排主关键字

如:010 102 110 212基数排序

先主后次

在这里插入图片描述

显然不能先主后次

先次后主

在这里插入图片描述
图中, d = 3 d=3 d=3 ,十位上最多有三个取值,所以 r = 3 r=3 r=3

特点

在这里插入图片描述

桶排序(计数排序)

适用于:对一定范围内的整数排序

# include<stdio.h>

void countSort(int nums[],int );

int main(){
    int nums[] = {2,3,1,4,3}; 

    countSort(nums,5);

    return 0;
}

void countSort(int nums[],int n){
    // 1. 确定桶数组
    //最大值是4,则需要0-4,即5个数组占用6个数组元素位置
    int max = 0;
    for(int i = 0;i < n;++i){
        if(max < nums[i])
            max = nums[i];
    }

    int bucket[max+1] = {0};

    // 3. 计数
    for(int i = 0;i < n;++i)
        bucket[nums[i]]++;
    
    for(int i = 0;i < max;++i)
        printf("%d ",i);
    printf("的数量\n");
    for(int i = 0;i < max;++i)
        printf("%d ",bucket[i]);
}

6.3 外部排序

6.3.1 存储空间

K路归并排序,内存中一个输出缓存区,K个输入缓存区,对r个元素进行排序

6.3.2 过程

1. 生成k个初始归并段

把K个归并段的块读入K个输入缓冲区

对每个输入缓冲区中的L个记录进行内部排序,组成K个有序的初始归并段

2. 进行S趟K路归并 S = ⌈ l o g k r ⌉ S=\lceil log_kr\rceil S=logkr

用归并排序的思想从K个归并段中选出各段最小记录,暂存到输出缓冲区

  • 当某一输入缓冲区为空,立马读入新块
  • 缓冲区不空才进行下一轮比较

输出缓冲区满,写出外存

6.3.3 时间开销

t 总时间 = t 读写外存 + t 内部排序 + t 内部归并 t_{总时间}=t_{读写外存}+t_{内部排序}+t_{内部归并} t总时间=t读写外存+t内部排序+t内部归并

  • 读写外出:归并多少趟,则读入多少次

  • 内部排序:可优化段数,但读写IO次数不变

    块内排序:构造初始归并段花费时间

  • 内部归并:K路中选择最值放入输出缓冲区

6.3.4 优化

增加归并路数

代价

增加响应的输入缓冲区

每次从k个归并段中选出一个最小元素需要 k − 1 k-1 k1 次关键字比较
S ( n − 1 ) ( k − 1 ) = ⌈ l o g k r ⌉ ( n − 1 ) ( k − 1 ) = ⌈ l o g 2 r ⌉ ( n − 1 ) ( k − 1 ) ⌈ l o g 2 r ⌉ S(n-1)(k-1) = \lceil log_kr\rceil(n-1)(k-1)=\frac{\lceil log_2r\rceil(n-1)(k-1)}{\lceil log_2r \rceil} S(n1)(k1)=logkr(n1)(k1)=log2rlog2r(n1)(k1)

内部归并优化

败者树,减少关键字比较次数

K路有K个叶

  • 树高 = 比较次数 = ⌈ l o g 2 K ⌉ 树高=比较次数=\lceil log_2K \rceil 树高=比较次数=log2K

总比较次数

  • S ( n − 1 ) ⌈ l o g 2 k ⌉ = ⌈ l o g k r ⌉ ( n − 1 ) ⌈ l o g 2 k ⌉ = ⌈ l o g 2 r ⌉ ( n − 1 ) S(n-1)\lceil log_2k \rceil = \lceil log_kr \rceil(n-1)\lceil log_2k \rceil = \lceil log_2r \rceil(n-1) S(n1)log2k=logkr(n1)log2k=log2r(n1)

k路平衡归并

最多k个段并为1个

每一趟若有m个段,处理完后会有 ⌈ m r ⌉ \lceil \frac{m}{r} \rceil rm

减少初始归并段数量

在这里插入图片描述

多路平衡归并

在这里插入图片描述

数据结构中的排序算法是指一种将一串数据按照特定顺序(通常是升序或降序)排列的方法。常见的排序算法有多种,每种都有其特点和适用场景: 1. **冒泡排序**(Bubble Sort):简单直观,通过不断交换相邻元素使得较大值逐渐“浮”到数组顶部。效率较低,适用于小规模数据。 2. **选择排序**(Selection Sort):每次从未排序部分找出最小(大)元素,放到已排序部分的末尾。不稳定,对大规模数据效率不高。 3. **插入排序**(Insertion Sort):类似于打扑克牌,将每个元素逐个插入到已排序的部分的适当位置。效率随着数据有序程度提高而提升。 4. **快速排序**(Quick Sort):采用分治策略,选取一个基准元素,将序列分为两部分,一部分小于基准,另一部分大于基准,然后递归地对这两部分进行排序。平均性能好,是一种常用的高效排序法。 5. **归并排序**(Merge Sort):同样基于分治,将数组分成两个子数组,分别排序后再合并。稳定,但需要额外的空间存储。 6. **堆排序**(Heap Sort):利用堆这种数据结构特性,构建最大堆或最小堆进行排序。原地操作,效率较高。 7. **计数排序**(Counting Sort):针对非负整数,统计每个元素出现次数,再根据次数重构有序序列。时间复杂度低至线性,但只能处理整数范围有限的情况。 8. **基数排序**(Radix Sort):按位数从最低位到最高位进行排序,适合数字类型的整数排序
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AmosTian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值