【C语言 - 数据结构】万字详解快速排序、归并排序(上)

简介: 【C语言 - 数据结构】万字详解快速排序、归并排序

一、快速排序的概念


1.1快排的定义


快速排序简称快排,快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。


1.2快排的动态图

1.gif


1.3快速排序的几种版本介绍


快排的基本思路


1、先找整个数组的key


2、找【begin, key-1】和【key + 1, end 】区间的key


3、再去重复递归左右区间,当区间只剩一个值或者不存在时就是最小规模的子问题。


1、hoare版本


1669441485404.jpg


2、挖坑法


1669441494768.jpg


挖坑法思路简介


第二个版本:挖坑法(PartSort)


右边找小,左边找大,右边先走


右边找到小与keyi的,然后停下来,右边的把值赋给


keyi这个位置,右边腾出一个空位


左边找大的然后赋值给这个空位


最后左右两个指针的相遇在一个空位,然后把keyi填进去


谁做keyi和谁先走不是固定的,


左边做keyi,右边先走


右边做keyi, 左边先走


3、前后指针法


1669441509014.jpg


前后指针法的思路简介:


cur指针在前面找小,找到比key小的值就++prev指针,交换prev和cur位置的值


prev和cur的关系


1、cur还没遇到比key大的值时,prev紧跟着cur一前一后


2、cur遇到比key大的值后,prev和cur之间间隔着一段比key大的值的区间


二、快速排序的递归实现


2.1 hoare版本的递归实现


有了前面的讲解,我们对于hoare版本的快速排序已经有了一定的了解了,我们现在实现其代码部分:(大家可以先理解我对hoare版本的定义再来看其实现代码,或者是结合起来理解)

int PartSort(int* a, int left, int right)
{
  int keyi = left;//key设置成最左边的数
  while (left < right)
  {
  //右边找小
  while (left < right && a[right] >= a[keyi])
    --right;
  //左边找大
  while (left < right && a[left] > a[keyi])//找大
    ++left;
  Swap(&a[left], &a[right]);
  }
  Swap(&a[keyi], &a[left]);
  return left;
}
void QuickSort(int* a, int begin, int end)
{
    //子区间相等只有一个值或者不存在那么就是递归结束的子问题
    if (begin >= end)
        return;
    int keyi = PartSort(a, begin, end);
    // [begin, keyi - 1]keyi[keyi + 1, end]
    QuickSort(a, begin, keyi - 1);
    QuickSort(a, keyi + 1, end);
}


贴一张图方便大家理解


1669441546620.jpg


2.2挖坑法的递归代码示例:


//挖坑法
int PartSort2(int* a, int left, int right)
{
    int key = a[left];
    //坑位
    int pit = left;
    while (left < right)
    {
        //右边先走,找小于key
        while (left < right && a[right] >= key )
        {
            --right;
        }
        a[pit] = a[right];
        pit = right;
        //左边走,找大于key
        while (left < right && a[left] <= key)
        {
            ++left;
        }
        a[pit] = a[left];
        pit = left;
    }
    a[pit] = key;
    return pit;
}
void QuickSort2(int* a, int begin, int end)
{
    //子区间相等只有一个值或者不存在那么就是递归结束的子问题
    if (begin >= end)
        return;
    int keyi = PartSort2(a, begin, end);
    // [begin, keyi - 1]keyi[keyi + 1, end]
    QuickSort(a, begin, keyi - 1);
    QuickSort(a, keyi + 1, end);
}

2.3前后指针法的递归代码示例


//前后指针法
int PartSort3(int* a, int left, int right)
{
    int keyi = left;//如果是a[left],则是局部变量,SWap后还是原来的值
    //left则是下标
    int prev = left, cur = left + 1;
    while (cur <= right)
    {
        if (a[cur] < a[keyi] && a[++prev] != a[cur])
            Swap(&a[prev], &a[cur]);
        ++cur;
    }
    Swap(&a[prev], &a[keyi]);
    return prev;
}
void QuickSort3(int* a, int begin, int end)
{
    //子区间相等只有一个值或者不存在那么就是递归结束的子问题
    if (begin >= end)
        return;
    int keyi = PartSort3(a, begin, end);
    // [begin, keyi - 1]keyi[keyi + 1, end]
    QuickSort(a, begin, keyi - 1);
    QuickSort(a, keyi + 1, end);
}


三、快速排序的非递归实现以及快排模板


3.1快排的非递归实现


快排的非递归应用场景是比较少的,因为快排也不是那么容易就爆栈,但是学习快排的非递归也能帮助我们更好地理解快排。


快排的非递归写法用C语言实现会相对复杂,因为快排的非递归需要利用栈来实现,但是C语言没有自己的STL库,所以要自己手写一个栈,相对比较麻烦些。


我们还是使用前后指针法来找key,然后用栈来实现递归的作用


栈的代码:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack//动态栈
{
  int* a;
  int top;//栈顶的位置
  int capacity;//容量
}ST;
STDataType StackTop(ST* ps);//返回栈顶的值
void StackInit(ST* ps);//初始化栈
void StackDestory(ST* ps);//销毁栈
void StackPop(ST* ps);//弹出
void StackPush(ST* ps, STDataType x);//插入
bool StackEmpty(ST* ps);//判断栈是否为空。
#include"Stack.h"
void StackInit(ST* ps)//栈的初始化
{
  assert(ps);
  ps->a = NULL;//a点的值指向空
  ps->top = 0;//栈底为0
  ps->capacity = 0;//空间为0
}
void StackDestory(ST* ps)
{
  assert(ps);
  free(ps->a);//把a释放掉
  ps->a = NULL;
  ps->capacity = ps->top = 0;
}
void StackPush(ST* ps, STDataType x)//入数据
{
  assert(ps);
  //满了就扩容
  if (ps->top == ps->capacity)//如果栈的栈顶恰好和容量相等就扩容
  {
  int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
  ps->a = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
  if (ps->a == NULL)
  {
    printf("realloc fail\n");
    exit(-1);
  }
  ps->capacity = newCapacity;//新的空间赋给旧的
  }
  ps->a[ps->top] = x;//栈顶插入x;
  ps->top++;//top++
}
void StackPop(ST* ps)
{
  assert(ps);
  assert(ps->top > 0);
  --ps->top;//top--就相当于删除操作
}
bool StackEmpty(ST* ps)
{
  assert(ps);
  //两种写法
  //if (ps->top > 0)
  //{
  //  return false;
  //}
  //else
  //{
  //  return true;
  //}
  return ps->top == 0;
}
STDataType StackTop(ST* ps)
{
  assert(ps);
  assert(ps->top > 0);
  return ps->a[ps->top - 1];//访问栈顶元素(这里因为top我们设为0,所以访问栈顶元素相当于top-1
}
int StackSize(ST* ps)
{
  assert(ps);
  return ps->top;
}


用前后指针加之栈来实现快排的代码:

快排的非递归写法

void QuickSort5(int* a, int begin, int end)
{
    ST st;
    StackInit(&st);
    //入栈
    StackPush(&st, begin);
    StackPush(&st, end);
    //栈是后进先出
    while (!StackEmpty(&st))
    {
        int right = StackTop(&st);
        StackPop(&st);
        int left = StackTop(&st);
        StackPop(&st);
        int keyi = PartSort3(a, left, right);
        //[left, keyi - 1][keyi + 1, right]
        if (left < keyi - 1)//还要继续入栈的条件
        {
            StackPush(&st, left);
            StackPush(&st, keyi - 1);
        }
        if (keyi + 1 < right)
        {
            StackPush(&st, keyi + 1);
            StackPush(&st, right);
        }
    }
    StackDestory(&st);
}
PartSort3
//前后指针法
int PartSort3(int* a, int left, int right)
{
    int mini = Getmini(a, left, right);
    Swap(&a[mini], &a[left]);
    int keyi = left;//如果是a[left],则是局部变量,SWap后还是原来的值
    //left则是下标
    int prev = left, cur = left + 1;
    while (cur <= right)
    {
        if (a[cur] < a[keyi] && a[++prev] != a[cur])
            Swap(&a[prev], &a[cur]);
        ++cur;
    }
    Swap(&a[prev], &a[keyi]);
    return prev;
}


3.2快排的模板(适用于算法竞赛)


它把key设为了中间值,这样好像是代码既短又是最优的情况。

#include <iostream>
using namespace std;
const int N = 100010;
int q[N];
void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;
    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}


四、快排的优化


4.1三数取中法优化快排


我们为什么要对快排进行优化?


原因:


有序的时候快排的时间复杂度是O(N^2)


数据量大时会爆栈


1669441649801.jpg


三数取中代码示例:比快排模板选key的可靠性要更高些

int Getmini(int* a, int left, int right)//三数取中
{
  int mid = left + right;
  //防止溢出可以写成int mid = left + (right - left) / 2;
  if (a[left] < a[mid])
  {
  if (a[mid] < a[right])
  {
    return mid;
  }
  else if (a[left] > a[right])
  {
    return left;
  }
  else
  {
    return right;
  }
  }
  else
  {
  if (a[mid] > a[right])
  {
    return mid;
  }
  else if (a[left] < a[right])
  {
    return left;
  }
  else
  {
    return right;
  }
  }
}
相关文章
|
3天前
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
|
29天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
29天前
|
C语言
数据结构基础详解(C语言):图的基本概念_无向图_有向图_子图_生成树_生成森林_完全图
本文介绍了图的基本概念,包括图的定义、无向图与有向图、简单图与多重图等,并解释了顶点度、路径、连通性等相关术语。此外还讨论了子图、生成树、带权图及几种特殊形态的图,如完全图和树等。通过这些概念,读者可以更好地理解图论的基础知识。
|
3天前
|
搜索推荐 C语言 C++
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
|
1月前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
1月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
3天前
|
算法 搜索推荐 C语言
【C语言篇】深入理解指针4(模拟实现qsort函数)
【C语言篇】深入理解指针4(模拟实现qsort函数)
11 2
|
3天前
|
C语言
【C语言】探索文件读写函数的全貌(三)
【C语言】探索文件读写函数的全貌