数据结构和算法——【排序】

目录

8.1排序的基本概念

8.2 插入排序

8.2.1直接插入排序

8.2.2折半插入排序

8.2.3希尔排序

8.3 交换排序

8.3.1冒泡排序

8.3.2快速排序

8.4选择排序

8.4.1简单选择排序

8.4.2堆排序


8.1排序的基本概念


排序:重新排列表中的元素,使表中元素满足按关键字有序的过程(关键字可以相同)
排序算法的评价指标:时间复杂度、空间复杂度;
排序算法的稳定性:关键字相同的元素在排序之后相对位置不变,称为稳定的;(选择题考查)
Q: 稳定的排序算法一定比不稳定的好?
A: 不一定,要看实际需求;
排序算法的分类:
内部排序: 数据都在内存——关注如何使时间、空间复杂度更低;
外部排序: 数据太多,无法全部放入内存——关注如何使时间、空间复杂度更低,如何使读/写磁盘次数更少;


8.2 插入排序


8.2.1直接插入排序


算法思想: 每次将一个待排序的记录按其关键字大小,插入(依次对比、移动)到前面已经排好序的子序列中,直到全部记录插入完成
代码实现:


不带“哨兵”
 

void InsertSort(int A[], int n){    //A中共n个数据元素
    int i,j,temp;
    for(i=1; i<n; i++)
        if(A[i]<A[i-1]){    //A[i]关键字小于前驱
            temp = A[i];  
            for(j=i-1; j>=0 && A[j]>temp; --j)
                A[j+1] = A[j];     //所有大于temp的元素都向后挪
            A[j+1] = temp;         //复制到插入位置
        }
}

  • 带“哨兵” ,优点:不用每轮循环都判断j>=0
void InsertSort(int A[], int n){    //A中从1开始存储,0放哨兵
    int i,j;
    for(i=1; i<n; i++)
        if(A[i]<A[i-1]){    
            A[0] = A[i];     //复制为哨兵
            for(j=i-1; A[0] < A[j]; --j)  //从后往前查找待插入位置
                A[j+1] = A[j];     //向后挪动
            A[j+1] = A[0];          //复制到插入位置
        }
}

算法效率分析


空间复杂度:O(1)
时间复杂度:主要来自于对比关键字、移动关键字,若有n个元素,则需要n-1躺处理
最好情况: 原本为有序,共n-1趟处理,每一趟都只需要对比1次关键字,不需要移动元素,共对比n-1次 —— O(n)
最差情况: 原本为逆序 —— O(n²)
平均情况: O(n²)
算法稳定性:稳定

对链表进行插入排序


移动元素的次数变少了,因为只需要修改指针,不需要依次右移;
但是关键字对比的次数依然是O(n²)数量级,因此整体看来时间复杂度仍然是O(n²)
 

8.2.2折半插入排序


思路: 先用折半查找找到应该插入的位置,再移动元素;

为了保证稳定性,当查找到和插入元素关键字一样的元素时,应该继续在这个元素的右半部分继续查找以确认位置; 即当 A[mid] == A[0] 时,应继续在mid所指位置右边寻找插入位置

当low>high时,折半查找停止,应将[low,i-1]or[high+1,i-1]内的元素全部右移,并将A[0]复制到low所指的位置;

代码实现

void InsertSort(int A[], int n){ 
    int i,j,low,high,mid;
    for(i=2;i<=n;i++){
        A[0] = A[i];                    //将A[i]暂存到A[0]
        low = 1; high = i-1;            //折半查找的范围

        while(low<=high){               //折半查找
            mid = (low + high)/2;       //取中间点
            if(A[mid]>A[0])             //查找左半子表
                high = mid - 1;
            else                        //查找右半子表
                low = mid + 1;
        }
        
        for(j=i-1; j>high+1;--j)       //统一后移元素,空出插入位置
            A[j+1] = A[j];
        A[high+1] = A[0]
    }
}

8.2.3希尔排序

  1. 思路: 先追求表中元素的部分有序,再逐渐逼近全局有序;

  2. 更适用于基本有序的排序表和数据量不大的排序表,仅适用于线性表为顺序存储的情况

  3. 代码实现:

void ShellSort(ElemType A[], int n){
    //A[0]为暂存单元
    for(dk=n/2; dk>=1; dk=dk/2)   //步长递减(看题目要求,一般是1/2
        for(i=dk+1; i<=n; ++i)
            if(A[i]<A[i-dk]){
                A[0]=A[i];
                for(j=i-dk; j>0&&A[0]<A[j];j-=dk)
                    A[j+dk]=A[j];         //记录后移,查找插入的位置
                A[j+dk]=A[0;]             //插入
            }
}

8.3 交换排序


**基于“交换”的排序:**根据序列中两个元素关键字的比较结果来对换这两个记录再序列中的位置;

8.3.1冒泡排序


第一趟排序使关键字值最小的一个元素“冒”到最前面(其最终位置)—— 每趟冒泡的结果是把序列中最小元素放到序列的最终位置,这样最多做n-1趟冒泡就能把所有元素排好序;

为保证稳定性,关键字相同的元素不交换;

代码实现

//交换
void swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;
}

//冒泡排序
void BubbleSort(int A[], int n){   //从0开始存放
    for(int i=0; i<n-1; i++){
        bool flag = false; // 表示本趟冒泡是否发生交换的标志
        for(int j=n-1; j>i; j--) //一趟冒泡过程
            if(A[j-1]>A[j]){      //若为逆序
                swap(A[j-1],A[j]);  //交换
                flag=true;
            }
        if(flag==false)
            return;       //本趟遍历后没有发生交换,说明表已经有序,可以结束算法
    }
}

算法效率分析
空间复杂度:O(1)
时间复杂度
最好情况 (有序) :只需要一趟排序,比较次数=n-1,交换次数=0,最好时间复杂度=O(n)
最坏情况 (逆序) :比较次数 = (n-1)+(n-2)+…+1 = n(n-1)/2 = 交换次数,最坏时间复杂度 = O(n²),平均时间复杂度 = O(n²)
冒泡排序可以用于链表、顺序表

 

8.3.2快速排序

  1. 每一趟排序都可使一个中间元素确定其最终位置
  2. 用一个元素(不一定是第一个)把待排序序列“划分”为两个部分,左边更小,右边更大,该元素的最终位置已确认
  3. 算法实现(重点)
    //用第一个元素将待排序序列划分为左右两个部分
    int Partition(int A[], int low, int high){
        int pivot = A[low];          //用第一个元素作为枢轴
        while(low<high){
            while(low<high && A[high]>=pivot) --high; //high所指元素大于枢轴,high左移
            A[low] = A[high];   //high所指元素小于枢轴,移动到左侧
    
            while(low<high && A[low]<=pivot)  ++low; //low所指元素小于枢轴,low右移
            A[high] = A[low];   //low所指元素大于枢轴,移动到右侧
        }
        A[low] = pivot   //枢轴元素存放到最终位置
        return low;     //返回存放枢轴的最终位置
    } 
    
    //快速排序
    void QuickSort(int A[], int low, int high){
        if(low<high)   //递归跳出条件
            int pivotpos = Partition(A, low, high);   //划分
            QuickSort(A, low, pivotpos - 1);    //划分左子表
            QuickSort(A, pivotpos + 1, high);   //划分右子表
    }
    
    

    8.4选择排序

    思想:每一趟在待排序元素中选取关键字最小(或最大)的元素加入有序子序列;

    8.4.1简单选择排序

    n个元素的简单选择排序需要n-1趟处理;

//交换
void swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;
}

void SelectSort(int A[], int n){       //A从0开始
    for(int i=0; i<n-1; i++){          //一共进行n-1趟,i指向待排序序列中第一个元素
        int min = i;                   //记录最小元素位置
        for(int j=i+1; j<n; j++)       //在A[i...n-1]中选择最小的元素
            if(A[j]<A[min]) min = j;   //更新最小元素位置
        if(min!=i)                     
            swao(A[i],A[min]);         //交换
    }
}

8.4.2堆排序


什么是“堆(Heap)”?
可理解为顺序存储的二叉树,注意
可以将堆视为一棵 完全二叉树 (✔)

可以将堆视为一棵 二叉排序树 (✖)

大根堆:完全二叉树中,根 ≥ 左、右
小根堆:完全二叉树中,根 ≤ 左、右

如何基于“堆”进行排序
基本思路:每一趟在待排序元素中选取关键字最小(或最大)的元素加入有序子序列,堆顶元素的关键字最大或最小 (以下以大根堆为例)

① 将给定初始序列(n个元素),建立初始大根堆:把所有非终端结点 从后往前都检查一遍,是否满足大根堆的要求——根 ≥ 左、右,若不满足,则将当前结点与更大的孩子互换

在顺序存储的完全二叉树中:

非终端结点的编号 i≤⌊n/2⌋
i 的左孩子 2i
i 的右孩子 2i+1
i 的父节点⌊i/2⌋
更小的元素“下坠”后,可能导致下一层的子树不符合大根堆的要求,则采用相同的方法继续往下调整 —— 小元素不断“下坠”

② 基于大根堆进行排序:每一趟将堆顶元素加入有序子序列中,堆顶元素与待排序序列中最后一个元素交换后,即最大元素换到末尾,之后该位置就不用改变,即移出完全二叉树(len=len-1),把剩下的待排序元素序列再调整为大根堆;————“一趟处理”

③ 剩下最后一个元素则不需要再调整;
 

//对初始序列建立大根堆
void BuildMaxHeap(int A[], int len){
    for(int i=len/2; i>0; i--)   //从后往前调整所有非终端结点
        HeadAdjust(A, i, len);
}

/*将以k为根的子树调整为大根堆
从最底层的分支结点开始调整*/
void HeadAdjust(int A[], int k, int len){
    A[0] = A[k];                      //A[0]暂存子树的根结点
    for(int i=2*k; i<=len; i*=2){     //沿key较大的子结点向下筛选
                                      // i为当前所选根结点的左孩子
                                      //i*=2是为了判断调整后再下一层是否满足大根堆
        if(i<len && A[i]<A[i+1])      //判断:当前所选根结点的左、右结点哪个更大
            i++;                      //取key较大的子结点的下标
        if(A[0] >= A[i]) 
            break;                    //筛选结束:i指向更大的子结点
        else{
            A[k] = A[i];              //将A[i]调整至双亲结点上
            k=i;                      //修改k值,以便继续向下筛选
        }
    }
    A[k] = A[0]                       //被筛选的结点的值放入最终位置
}

//交换
void swap(int &a, int &b){
    int temp = a;
    a = b;
    b = temp;
}

//基于大根堆进行排序
void HeapSort(int A[], int len){
    BuildMaxHeap(A, len);          //初始建堆
    for(int i=len; i>1; i--){      //n-1趟的交换和建堆过程
        swap(A[i], A[1]);          //堆顶元素和堆底元素交换
        HeadAdjust(A,1,i-1);       //把剩余的待排序元素整理成堆
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值