算法导论-------快速排序QuickSort

目录:


一、快速排序思想介绍

  快速排序(QuickSort)是对冒泡排序(BubbleSort)的一种改进。排序效率在同为O(N*logN)的几种排序方法中效率较高,再加上快速排序算法是 分治策略(Divide-and-ConquerMethod)的典型应用。因此很多软件公司的笔试面试,还有大大小的程序方面的考试中也常常出现快速排序的身影。博主就在蓝桥杯竞赛上遇到过。

  快速排序由图灵奖得主C. A. R. Hoare在1960年提出。它的 基本思想是:通过一趟排序将要排序的数据分割成独立的三个序列:第一个序列中所有的元素均不大于基准元素、第二个序列是基准元素、第三个序列中所有的元素均不小于基准元素。由于第二个序列已经处于正确位置,因此需要再按此方法对第一个和第三个序列分别进行排序,整个排序过程可以递归进行,最终可以使得整个序列变成有序序列。


二、实现的三步骤(分解、子问题求解、合并)

  快速排序算法的基本思想是基于分治策略的,利用分治可将快速排序的基本思想描述如下:设当前待排序的序列为R[ low : high ] ,其中low <= high,如果序列的规模足够小则直接进行排序,否则分三步处理:
1、分解
  在R[ low :high ]中选定一个元素作为基准元素(pivot),该基准元素的最终的位置(pivotpos)在划分的过程中确定。将比R[ pivotpos]大的数全放到它的右边R[pivotpos+1 : high],小于或等于它的数全放到它的左边R[low : pivotpos-1 ]。

注意:基准元素如何选定,选哪个元素?基准元素最终的排序位置,在划分的过程中确定,如何确定?不要着急,下面讲解。

2、求解子问题
  对两个子序列R[low :pivotpos-1 ]和R[pivotpos+1 : high]分别通过递归调用快速排序。

3、合并
  由于对子序列R[low :pivotpos-1 ]和R[pivotpos+1 : high]的排序是就地进行的,所以在子序列R[low : pivotpos-1 ]和R[pivotpos+1 : high]都排序结束后,合并步骤无须做什么,整个序列R[ low : high ]就排好序了。

基准元素(pivot)的选取。

  快速排序要选定基准元素,选取基准元素应该遵循平衡子问题的原则:即使得划分后的两个子序列的长度尽量相同。基准元素的选择方法有很多种,常见的方式是把待排序列的 首元素作为基准元素 。本篇对以首元素为基准元素的方法给出详解;以尾元素为基准元素的单向扫描法仅给出代码。

基准元素最终位置(pivotpos)的确定 【一般书上都把该功能定义在partition函数中】
  快速排序中基准元素对序列进行划分,从而实现分治。假定待排序列为R[ low : high ],该划分过程以第一个元素为基准元素。

  1. 设定两个参数i和j,他们的初值分别为待排序列的下界和上界,即i=low,j=high。
  2. 选取待排序列的第一个元素R[low]为基准元素,并将该值赋值给变量pivot。
  3. 令j自j位置开始向左扫面,如果j位置所对应的元素的值大于等于pivot,则j前移一个位置(即j- -)。重复该过程,直到找到第一个小于pivot的元素R[j],将R[j]和R[i]进行交换,i++。 其实交换后R[j]所对应的元素就是pivot。
  4. 令i自i位置开始向右扫描,如果i位置所对应的元素的值小于等于pivot,则i后移(即i++)。重复该过程,直至找到第一个大于pivot的元素R[i],将R[i]与R[j]进行交换,j–。其实交换后R[i]所对应的元素就是pivot。
  5. 重复步骤3、4,交替改变扫描方向,从两端各自往中间靠拢直至i==j。此时i和j指向同一个位置,即基准元素pivot的最终位置。

   《算法导论》上选择数组的尾元素为基准元素,采用单向扫描法确定pivotpos最终的位置。本篇博文也给出实现。但不再展开细谈。


三、C代码实现如下:


  在快速排序中,partition函数有多种实现方式,主要分为“双向扫描”和“单向扫描”,在双向扫描中也有几种不同的实现方式。《算法导论》书上还提供了随机化版本、三数取中划分等版本,由于篇幅受限,不做介绍。

3.1 快速排序双向扫描法(一)

#include <stdio.h>
#define MAX 100
int array[MAX];

//交换数组中两个元素位置
void swap(int *a,int *b) {
    int temp=*a;
    *a=*b;
    *b=temp;
}

int partition(int * Arr,int low,int high) {  //划分方法
    //i和j分别指向数组下界和上界,pivot是待排的第一个元素
    int i=low,j=high,pivot=Arr[low];
    while (i<j) {
        /* j自j位置开始向左扫面,如果j位置所对应的元素的值大于等于pivot,则j前移一个位置(即j--)。
        重复该过程,直到找到第一个小于pivot的元素R[j],将R[j]和R[i]进行交换,i++。
        其实交换后R[j]所对应的元素就是pivot。*/
        while (i<j && Arr[j]>=pivot) {
            j--;
        }
        if (i<j) {
            swap(&Arr[i++],&Arr[j]);//注意这里是交换元素,另外还有挖坑法实现,是元素覆盖。
        }
        /* 令i自i位置开始向右扫描,如果i位置所对应的元素的值小于等于pivot,则i后移(即i++)。
        重复该过程,直至找到第一个大于pivot的元素R[i],将R[i]与R[j]进行交换,j--。
        其实,交换后R[i]所对应的元素就是pivot。*/
        while (i<j  && Arr[i]<=pivot) {
            i++;
        }
        if (i<j) {
            swap(&Arr[i],&Arr[j--]);
        }
    }
    /*此时i和j指向同一个位置,即基准元素pivot的最终位置。返回i的值*/
    return  i;
}
void   QuickSort(int * Arr,int low,int high) {  //对数组Arr[low  high]进行快速排序
    int pivotpos;                               //划分的基本元素所在的位置
    if(low<high) {                              //区间长度大于1时才排序
        pivotpos=partition(Arr,low,high);       //对Arr[low high]进行划分
        QuickSort(Arr,low,pivotpos-1);
        QuickSort(Arr,pivotpos+1,high);
    }
}

void PrintArray(int arr[],int length) {         //打印数组,用于查看排序效果
    printf("[");
    for(int i=0; i<length; i++) {
        if(i==length-1)
            printf("%d]\n",arr[i]);
        else
            printf("%d ,",arr[i]);
    }
}

int main() {
    int num=0;
    printf("请输入数组元素个数:");
    scanf("%d",&num);

    for(int i=0; i<num; i++) { //读入待排序数据
        scanf("%d",&array[i]);
    }

    printf("排序前数据为:");
    PrintArray(array,num);

    QuickSort(array,0,num-1);

    printf("排序后数据为:");
    PrintArray(array,num);
    return 0;
}

3.2 partition函数双向扫描法(二)

int Partition(int * Arr,int low,int high)     
{    
    int i = low, j = high;    
    int pivot = Arr[low]; //Arr[low]已经保存,可以被覆盖,即第一个坑    
    while (i < j)    
    {    
        // 从右向左找小于pivot的数来填Arr[low]    
        while(i < j && Arr[j] >= pivot)     
            j--;      
        if(i < j)  
        {//将Arr[j]填到Arr[i]中,Arr[j]就形成了一个新的坑.  
            //这里不再是交换元素位置。  
            Arr[i] = Arr[j];     
            i++;    
        }  

        // 从左向右找大于或等于pivot的数来填Arr[j]    
        while(i < j && Arr[i] < pivot)    
            i++;      
        if(i < j)     
        {    
            Arr[j] = Arr[i]; //将Arr[i]填到Arr[j]中,Arr[i]就形成了一个新的坑    
            j--;    
        }    
    }    
    //退出时,i等于j。将pivot填到这个坑中    
    Arr[i] = pivot;    
    //返回调整后基准数的位置  
    return i;    
} 

3.3 partition函数双向扫描法(三)

int Partition(int num[], int left, int right) {
    int index = left + (right-left>>1);//注意:移位操作的优先级比加减法运算低
    int temp = num[index];
    num[index] = num[left];
    while(left < right) {
        while(left < right && temp <= num[right])
            right--;
        num[left] = num[right];
        while(left < right && temp > num[left])
            left++;
        num[right] = num[left];
    }
    num[left] = temp;
    return left;
}

3.4 partition函数单向扫描法

这里写图片描述

这里写图片描述

int partition (int arr[], int p, int r)
{
    int i = p - 1;
    for (int j = p; j < r; j++)
    {
        if (arr[j] < arr[r])
        {
            swap(&arr[++i],&arr[j]);
        }
    }
    swap(&arr[++i],&arr[r]);
    return i;
}

四、时间空间复杂度分析

   空间复杂度:快速排序算法是递归执行,需要一个栈来存放每一层递归调用的必要信息,其最大容量应与递归调用的深度一致。最好的情况下,每次划分较为均匀,递归树的深度为O(logN),故递归所需要的栈空间为O(logN)。最坏情况下,递归树的高度为O(N),所需的栈空间为O(N)。平均情况下,所需栈空间为O(logN)。
   时间复杂度: O(N*logN)。


五、动画演示

  鄙人才疏学浅,制作不出动画演示。在此推荐日本程序员norahiko写的一个排序算法的动画演示,请戳链接h ttp://jsdo.it/norahiko/oxIy/fullscreen

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
第一部分(Part I) 基础(Foundations) 第一章 计算中算法的角色(The Role of Algorithms in Computing) 第二章 开始(Getting Started) 第三章 函数的增长率(Growth of Functions) 第四章 递归(Recurrences) 第五章 概率分析与随机化算法(Probabilistic Analysis and Randomized Algorithms) 第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics) 第六章 堆排序(Heapsort) 第七章快速排序Quicksort) 第八章 线性时间中的排序(Sorting in Linear Time) 第九章 中值与顺序统计(Medians and Order Statistics) 第三部分(Part III) 数据结构(Data Structures) 第十章 基本的数据结构(Elementary Data Structures) 第十一章 散列表(Hash Tables) 第十二章 二叉查找树(Binary Search Trees) 第十三章 红-黑树(Red-Black Trees) 第十四章 扩充的数据结构(Augmenting Data Structures) 第四部分(Part IV) 高级的设计与分析技术(Advanced Design and Analysis Techniques) 第十五章 动态规划(Dynamic Programming) 第十六章 贪婪算法(Greedy Algorithms) 第十七章 分摊分析(Amortized Analysis) 第五部分(Part V) 高级的数据结构(Advanced Data Structures) 第十八章 B-树(B-Trees) 第十九章 二项式堆(Binomial Heaps) 第二十章 斐波纳契堆(Fibonacci Heaps) 第二十一章 不相交集的数据结构(Data Structures for Disjoint Sets) 第六部分(Part VI) 图算法(Graph Algorithms) 第二十二章 基本的图算法(Elementary Graph Algorithms) 第二十三章 最小生成树(Minimum Spanning Trees) 第二十四章单源最短路径(Single-Source Shortest Paths) 第二十五章 全对的最短路径(All-Pairs Shortest Paths) 第二十六章 最大流(Maximum Flow) 第七部分(Part VII) 精选的主题(Selected Topics) 第二十七章 排序网络(Sorting Networks) 第二十八章矩阵运算(Matrix Operations) 第二十九章 线性规划(Linear Programming) 第三十章 多项式与快速傅里叶变换(Polynomials and the FFT) 第三十一章 数论算法(Number-Theoretic Algorithms) 第三十二章 字符串匹配(String Matching) 第三十三章 计算几何学(Computational Geometry) 第三十四章 NP-完备性(NP-Completeness) 第三十五章 近似算法(Approximation Algorithms) 第八部分(Part VIII) 附录:数学背景(Mathematical Background) 索引(Index)
C语言是一种广泛应用于系统编程和嵌入式开发的高级编程语言。函数指针是C语言中的一个重要概念,它可以指向函数,并且可以通过函数指针来调用函数。快速排序算法是一种常用的排序算法,它的核心思想是通过分治的策略将一个大问题分解为多个小问题,并通过递归的方式解决这些小问题。 下面是C语言中使用函数指针实现快速排序算法的示例代码: ```c #include <stdio.h> // 交换两个元素的值 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 快速排序的分区函数 int partition(int arr[], int low, int high) { int pivot = arr[high]; // 选取最后一个元素作为基准 int i = (low - 1); // 定义一个指针,用于指向小于基准的元素 for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } // 快速排序函数 void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); // 将数组分区,并获取分区点的位置 quickSort(arr, low, pi - 1); // 对分区点左边的子数组进行快速排序 quickSort(arr, pi + 1, high); // 对分区点右边的子数组进行快速排序 } } // 打印数组元素 void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {10, 7, 8, 9, 1, 5}; int n = sizeof(arr) / sizeof(arr[0]); printf("原始数组:"); printArray(arr, n); quickSort(arr, 0, n - 1); printf("排序后的数组:"); printArray(arr, n); return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值