[C/C++基础知识] 一篇就让你彻底搞懂qsort快速排序的文章

        最近在做LeetCode的题目、面试和笔试后发现经常考察快速排序的知识。通过这篇文章介绍,能让你彻底的了解和学习快排,主要从一下三个部分进行介绍:
        一.C语言实现qsort快速排序
        二.快速排序的原理及手写快排源码
        三.LeetCode关于Two Sum的快排实现
参考文献:
       《算法分析与设计》关于分治法那章内容
        如何利用C语言中的qsort库函数实现快速排序 - by:stpeace
        白话经典算法系列之六 快速排序 快速搞定 - by:MoreWindows
        https://leetcode.com/problems/two-sum/
        [leetcode] Two Sum  by:阳光岛主
        

一. C语言实现qsort快速排序

        这段介绍参考百度百科,编译器函数库自带的快速排序函数qsort。使用qsort()排序并用 bsearch()搜索是一个比较常用的组合,使用方便快捷。
        头文件:stdlib.h
        用 法: void qsort(void *base, int nelem, int width, int (*fcmp)(const void *, const void *));
        参数: 1 待排序数组首地址
                   2 数组中待排序元素数量
                   3 各元素的占用空间大小
                   4 指向函数的指针,用于确定排序的顺序

        整数快排
        int cmp1(const void *a, const void *b)  
        {  
               return
*(int*)a - *(int*)b;  
        } 
        qsort(num, len, sizeof(int), cmp1);

#include <stdio.h>
#include <stdlib.h>  

//整数从小到大排序
int cmp1(const void *a, const void *b)
{
    return *(int*)a - *(int*)b;
}
//整数从大到小排序
int cmp2(const void *a, const void *b)
{
    return *(int*)b - *(int*)a;
}  

int main()
{
    int num[8] = {4,11,2,-3,15,4,0,7};
    int len = 8;
    int i;  

    printf("整数从小到大排序:\n");
    qsort(num, len, sizeof(int), cmp1);
    for(i = 0; i < len; i ++)
    {
        printf("%d ", num[i]);
    }
    printf("\n\n");  

    printf("整数从大到小排序:\n");
    qsort(num, len, sizeof(int), cmp2);
    for(i = 0; i < len; i ++)
    {
        printf("%d ", num[i]);
    }
    printf("\n");  

	system("PAUSE");
    return 0;
}   

        运行结果如下图所示:

        二维数组快排
        int cmp(const void *a, const void *b)  
        {  
             return
((int*)a)[0] - ((int*)b)[0];  
        }
        qsort(num, len, sizeof(int)*2, cmp);

#include <stdio.h>
#include <stdlib.h>  

//二维数组按照num[i][0]从小到大排序
int cmp(const void *a, const void *b)
{
    return ((int*)a)[0] - ((int*)b)[0];
}  

int main()
{
    int num[6][2] = {
		4, 1,
	   11, 2,
		2, 3,
	   -3, 4,
	   15, 5,
	    0, 7,
	};
    int len = 6;
    int i;  

    printf("排序前:\n");
	for(i = 0; i < len; i ++)
    {
        printf("%4d %4d\n", num[i][0], num[i][1]);
    }
	printf("\n"); 

    qsort(num, len, sizeof(int)*2, cmp);   

    printf("排序后:\n");
    for(i = 0; i < len; i ++)
    {
        printf("%4d %4d\n", num[i][0], num[i][1]);
    }
    printf("\n");  

	system("PAUSE");
    return 0;
}  

        运行结果如下图所示,其中num[i][0]和num[i][1]同时移动。

        字符串快排
        int cmp(const void *a, const void *b)  
        {  
             return *(char*)a - *(char*)b;  
        }   
        qsort(str, sum, sizeof(char)*10, cmp);

#include <stdio.h>
#include <stdlib.h>  

int cmp(const void *a, const void *b)
{
    return *(char*)a - *(char*)b;
}  

int main()
{
    char str[7][10] = {
		"Monday",
		"Tuesday",
		"Wednesday",
		"Thursday",
	    "Friday",
	    "Staturday",
	    "Sunday"
	};
    int sum = 7;
    int i;  

    printf("排序前:\n");
	for(i = 0; i < sum; i++)
    {
        printf("%s\n", str[i]);
    }
	printf("\n"); 

    qsort(str, sum, sizeof(char)*10, cmp);   

    printf("排序后:\n");
    for(i = 0; i < sum; i++)
    {
         printf("%s\n", str[i]);
    }
    printf("\n");  

	system("PAUSE");
    return 0;
}   

        同int类型一样,输出如下所示:

        double快排
        int cmp(const void *a, const void *b)  
        {  
            return *(double*)a > *(double*)b ? 1 : -1;  
        }  
        qsort(num, sum, sizeof(double), cmp); 

#include <stdio.h>
#include <stdlib.h>  

int cmp(const void *a, const void *b)
{
    return *(double*)a > *(double*)b ? 1 : -1;
}  

int main()
{
    double num[7] = {
		12.2324,
		-4.3457,
		0.00000,
		5.64375,
	    1.25879,
	    0.00001,
	    7.85435
	};
    int sum = 7;
    int i;  

    printf("排序前:\n");
	for(i = 0; i < sum; i++)
    {
        printf("%10f\n", num[i]);
    }
	printf("\n"); 

    qsort(num, sum, sizeof(double), cmp);   

    printf("排序后:\n");
    for(i = 0; i < sum; i++)
    {
         printf("%10f\n", num[i]);
    }
    printf("\n");  

	system("PAUSE");
    return 0;
}   

        输出如下图所示:

        结构体排序
        int compare1(const void *a,const void *b)  
        {  
            return ((Student*)a)->score - ((Student*)b)->score;  
        }
        qsort(s, N, sizeof(Student), compare1);

#include <stdio.h>
#include <stdlib.h>
#define N 6  

typedef struct
{
    char name[15];
    int  score;  

}Student;  

int compare1(const void *a,const void *b)
{
    return ((Student*)a)->score - ((Student*)b)->score;
}  

int compare2(const void *a,const void *b)
{
    return *(((Student*)a)->name) - *(((Student*)b)->name);
}  

int main()
{
    Student s[N] =
    {
    "Zhang San", 94,
    "Li Si",     80,
    "You",       94,
    "I",        100,
    "He",        72,
    "She",       60
    };  

    int i;
	printf("按照分数排序:\n");
    qsort(s, N, sizeof(Student), compare1);
    for(i = 0; i < N; i++)
    {
        printf("%-15s : %d\n", s[i].name, s[i].score);
    }
    printf("\n");  

    qsort(s, N, sizeof(Student), compare2);
	printf("按照姓名排序:\n");
    for(i = 0; i < N; i++)
    {
        printf("%-15s : %d\n", s[i].name, s[i].score);
    }
	system("PAUSE");
    return 0;
}  

        代码输出如下图所示:

二. 快速排序原理及手写快排源码

        由于MoreWindows写的白话文系列太好,所以这部分主要参考他的文章和《算法设计与分析》关于分治法那章内容。强烈推荐大家阅读系列文章,原文链接:
        http://blog.csdn.net/morewindows/article/details/6684558
        分治法的思想将一个大问题分割成小规模问题各个击破,分而治之,常用递归解决。常用的包括二分查找、归并排序和快速排序等应用。其中思想如下所示:
        divide-and-conquer(P)
        {
                  if(|P|<=n0) adhoc(p);             //解决小规模问题
                            divide P into smaller subinstances P1,P2…Pk;
                  for(i=1; i<=k; i++)  {
                            yi = divide-and-conquer(Pi); //分解
                  }
                  return merge(y1,y2…yk);         //合并子算法
        }
        关于快速排序,由于它的时间复杂度在O(N*logN)效率较高,经常使用。该算法的基本思想:
        1. 先从数列中取出一个数作为基准数。
        2. 分区过程,将比这个数大的全放到它右边,小于或等于它的全放到它的左边。
        3. 再对左右区间重复第二步,直到各区间只有一个数。
        MoreWindows对快速排序作了进一步的说明:挖坑填数+分治法 
        以一个数组作为示例,取区间第一个数为基准数。

        初始时,i = 0; j = 9;   X = a[i] = 72
        由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
        从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++;  这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?
        简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--; 数组变为:

        i = 3;   j =7;   X=72
        再重复上面的步骤,先从后向前找,再从前向后找。
        
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
        从i开始向后找,当i=5时,由于i==j退出。此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。数组变为:

        可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
        对挖坑填数进行总结:
        1. i =L; j =R; 将基准数挖出形成第一个坑a[i]。
        2. j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
        3. i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
        4. 再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
        照着这个总结很容易实现挖坑填数的代码:
        分治法:

void quick_sort1(int s[], int l, int r)
{
    if (l < r)
    {
        int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
        quick_sort1(s, l, i - 1); // 递归调用
        quick_sort1(s, i + 1, r);
    }
}  

       挖坑填数:

int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置
{
    int i = l, j = r;
    int x = s[l]; //s[l]即s[i]就是第一个坑
    while (i < j)
    {
        // 从右向左找小于x的数来填s[i]
        while(i < j && s[j] >= x)
            j--;
        if(i < j)
        {
            s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
            i++;
        }  

        // 从左向右找大于或等于x的数来填s[j]
        while(i < j && s[i] < x)
            i++;
        if(i < j)
        {
            s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
            j--;
        }
    }
    //退出时,i等于j。将x填到这个坑中。
    s[i] = x;  

    return i;
} 

       PS:去一些大公司面试,经常会让你手写快排、二叉树非递归遍历、链表翻转等

三. LeetCode关于Two Sum的快排实现

        下面讲一个经典的题目。做过LeetCode的肯定做个这个题目,LeetCode的第一题Two Sum,求两个数的和。链接:https://leetcode.com/problems/two-sum/

Given an array of integers, find two numbers such that they add up to a specific target number.

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers
(both index1 and index2) are not zero-based.

You may assume that each input would have exactly one solution.

Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2

        找到两个数字和为target,并输出两个值的下标。由于数组可能是无序状态,故需要排序后输出即可 。但你需要注意一下几点:
        1.首先想到的方法是两层循环遍历普通排序,时间复杂度O(N^2)会TLE
        2.输出下标不是从0开始,故i+1即可
        3.输出时负数需要判断小的在前
           易错:Input:[-1,-2,-3,-4,-5] -8  Output:[5,3]  Expected:[3,5]
        4.错误 Input:[3,2,4] 6  Output:[1,3]  Expected:[2,3]输出原始位置,所以使用快速排序怎样解决呢?方案一采用结构,方案二采用二维数组调用qsort:
              (1)先定义结构NODE存储<值,下标>在进行qsort排序
              (2)再一层循环前后两个指针移动遍历求和,时间复杂度O(N*logN)
         由于结果唯一,可以通过左右下标移动实现,如果sum<target移动左下标
        5.通过Hash实现时间复杂度O(n)
       
代码如下:

//定义结构 因为需要数组值和下标同时排序
typedef struct NODE {
    int value;
    int pos;
}node;

//调用cmp排序实现结构排序
int cmp(const void *a, const void *b)
{
    return ((node *)a)->value - ((node *)b)->value;
}

int* twoSum(int* nums, int numsSize, int target) {
    int *result;
    int i,j;
    int sum;
    node *numNode;
    result=(int*)malloc(sizeof(int)*2);
    numNode=(node*)malloc(sizeof(node)*numsSize);

    for(i=0; i<numsSize; i++) {
        numNode[i].value=nums[i];
        numNode[i].pos=i;
    }

    qsort(numNode,numsSize,sizeof(node),cmp);

    for(i=0,j=numsSize-1; i<j; ) {
        sum=numNode[i].value+numNode[j].value;
        if(sum==target) {
            if(numNode[i].pos<numNode[j].pos) { //小在前
                result[0]=numNode[i].pos+1;
                result[1]=numNode[j].pos+1;
                break;
            }
            else {
                result[0]=numNode[j].pos+1;
                result[1]=numNode[i].pos+1;
                break;
            }
        }
        else if(sum<target) { //左移
            i++;
        }
        else { //右移
            j--;
        }
    }
    return result;
}

        当然你也可以调用unordered_map实现哈希表O(n)算法:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> vec;
        unordered_map<int,int> map;
        for(int i=0; i<nums.size(); i++){
            if(map.find(target-nums[i]) != map.end()){
                vec.push_back(map[target-nums[i]]+1);
                vec.push_back(i+1);
                return vec;
            }
          map[nums[i]]=i;
        }
    }
};

        希望文章对你有所帮助吧!如果有错误或不足之处,还请海涵。最后分析一个今天的故事:
        今天去一个小公司面试(百度百科没有该公司词条),那个Leader感觉很不尊重人,一方面总是不屑的摇头与嘲笑人,一方面又总和旁边的人聊天,还不断打断人说话。确实我普通话带有西南口音,但是我就不明白了,难道这能影响到我写程序吗?虽然他直言不讳的说我编程语言不说精通,连熟悉都达不到,但是我前面都说了我不精通任何东西,只是一般。让我给他讲知识图谱,他说要用通俗的话,那我就举例子乔布斯、苹果等,他又说没难度,那我就讲神经网络,他说他不会编程。我不知道怎么说了~他居然还说百度可能仅仅是通过传统的搜索引擎现在返回一个值,如姚明的身高,而没有真正去做这个知识图谱,我不知道怎么吐槽。
         仅仅面试了10分钟,我就想走了,后面的问题我都说不知道,问我Python的优势,我说脚本语言、开发快、语言简洁,面向对象。他非说是面向过程的,和我争论怎么是面向对象的,我说举个例子建房子,他又说那是老师教的,我让你讲实例。好吧!我承认我比较弱,但是Python是面向对象这种常识都不知道,我只能呵呵了~
         然后他说你这也不会,那也不会,那就简单讲讲你认为自己最优秀的地方。我就说我最大的优点就是坚持,不论10年,5年也好,我能做出东西来;同时以后还是想会贵州当老师。他最后说我们要的人还是有要求的,明确告诉你不会给你offer,我当时也回了句:我也不会来的。
        最后离开的时候我给HR说我就是很不爽他,就没心思答了,而且我表达能力也不好,但还是非常感谢你的推荐。最后当得知他是国内最好的大学毕业的时候,我也非常震惊。我脾气只有这么好了,二十几年的光阴里没怎么生过气,当时确实生气。只恨脸皮薄,当时没损他几句,但那又显得不够大度。
        其实我想说:面试本身就是一个相互学习的过程,确实很多基础知识我都不记得了,可能也不善言表,但是你一个面试官不尊重面试人员,那我就是看不起你。不论你赚再多的钱,再有本事,学校再厉害,我就是看不起,做人似乎比工作更重要吧!我也大大小小参加了不少的面试,阿里、360、百度都有,各种大牛也遇到过,马云、吴恩达、刘知远的视频和讲座也看过不少,清华北大也都有同学,但是没见过这么不尊重人的,面试过程中又嘲讽又聊天的,各种秀英语,还吐槽我普通话。
        先做人,后做事吧!看着比我年轻几岁,这种秀优越感只能理解为不成熟。
        如果他们公司都是这样招人的,那早晚也会毁在他们手上的,虽然他们公司现在有很大的潜力;如果我还在他说下干活,遇到个东西整天吐槽你做得不够好,给他说代码他又说不会,我也早晚会辞职的。总之,以后如果我走到当面试官那一步了,最起码要尊重面试人员,因为你是在寻财,而不是秀优越感!而且我都不知道哪里来的优越感,学校吗?好吧!但我还是看不起你这个人~
        当然面试也让我知道了自己还存在很多不足,尤其是底层的算法很多都忘了,最近扎扎实实看书和做LeetCode吧!自己技术还是不够精通,非常致命,包括同步异步通讯,线程进程代码等~但还是感谢那个热心的HR吧!以后也尽量别参加这种没有笔试,通过猎头直接去小公司面试了。
      (By:Eastmount 2015-10-11 清晨6点 http://blog.csdn.net/eastmount/

  

时间: 2024-09-25 08:25:18

[C/C++基础知识] 一篇就让你彻底搞懂qsort快速排序的文章的相关文章

[Python学习] 专题四.文件基础知识

        前面讲述了函数.语句和字符串的基础知识,该篇文章主要讲述文件的基础知识(与其他语言非常类似). 一. 文件的基本操作         文件是指存储在外部介质(如磁盘)上数据的集合.文件的操作流程为:         打开文件(读方式\写方式)->读写文件(read\readline\readlines\write\writelines)->关闭文件         1.打开文件         调用函数open打开文件,其函数格式为:                      

C# 基础知识 (一).概念与思想篇

在C#中有一些我自己认为比较独特的知识点,这些知识点是我经常使用的知识,但对它们的了解还是比较少的,所以通过查找资料学习,总结了这些独特的知识点并简单叙述,第一篇主要是一些概念和思想方面的知识.(后面还有C#其他篇的文章) 一.C#概念 C#语言是从C和C++语言演变而来的,是微软创建的一门面向对象.运行在.NET Framework上的高级程序语言,是Windows的一个必要组件,包括一个称为公共语言运行时(common language runtime,CLR)的虚拟执行系统和一组统一的类库

《Sony Vegas Pro 12标准教程》——第1章 基础篇——基础知识 1.1 影视剪辑的概念

第1章 基础篇--基础知识 在你拿起这本书的时候,我想,你应该已经准备好创作属于自己的影片了:同时,也选择了Sony Vegas作为你的剪辑软件.你的心里既充满了期待,也充满了迷惘.没错,剪辑不仅仅是软件本身的功能,同时也代表了你对于视频画面的深层感悟.那么,即刻起,无论你是一名新手,还是曾经拥有属于自己的影片,都请走进Sony Vegas的世界,去看看Vegas能为你的影片带来哪些令人惊叹的效果. 本章学习要点 了解剪辑的概念与技法 掌握场序.分辨率.宽高比知识 熟悉音视频格式与编解码器 掌握

ASP基础入门第二篇(ASP基础知识)_应用技巧

本篇将继续介绍一些用 ASP 编写的WEB 动态功能.由于 WEB 浏览器标准的不一致从而使得如何能够让自己制作的网站去适应各种不同的浏览器成为了广大网站设计者最为头疼的事,在如今的形势之下,我们不肯也不可能去抛弃Netscape 或 IE 中的任何一种客户群,但我们有时候又不得不去考虑客户端浏览器的实际浏览效果,过去我们常用JavaScript 编写一段程序来辨别客户端使用的不同的浏览器,那么今天就让我们来看看如何使用ASP 更为便捷且精确地达到这一目的.将以下代码,剪贴到你的Notebook

iOS10推送之基础知识(必看篇)_IOS

前言 在北京时间9月14号凌晨1点,苹果正式推送iOS 10正式版,下面给大家详细的介绍iOS10推送的基础知识,在看完简单入门篇大家就可以简单适配了,然后再通过中级篇的内容,相信对大家学习理解有很大的帮助,下面话不多说了,来看看吧. 一.简单入门篇 相对简单的推送证书以及环境的问题,我就不在这里讲啦,我在这里说的,是指原有工程的适配. 1.首先我们需要打开下面的开关.所有的推送平台,不管是极光还是什么的,要想收到推送,这个是必须打开的哟~ 之后,系统会生成一个我们以前没见过的文件,如图: 可能

《机器人构建实战》——第1篇 基础知识

第1篇 基础知识 机器人构建实战第1章 绪论第2章 CDS5516数字舵机调试与参数设置第3章 图形化软件的使用

大数据基础知识问答----spark篇,大数据生态圈

Spark相关知识点 1.Spark基础知识 1.Spark是什么? UCBerkeley AMPlab所开源的类HadoopMapReduce的通用的并行计算框架 dfsSpark基于mapreduce算法实现的分布式计算,拥有HadoopMapReduce所具有的优点:但不同于MapReduce的是Job中间输出和结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的map reduce的算法. 2.Spark与Hadoop的对比(Spar

RESTful_基础知识

前言 本篇主要是RESTful的基础知识整理,主要是为了将要开始的Openstack架构主题做知识积累.理解好RESTful的设计思想无论是对学习Openstack架构还是Openstack Dashboard实现都是一件事半功倍的事情. RESTful REST(Representational State Transfer):是一种软件架构的设计风格,而不是一种标准.主要用于C/S架构的软件设计,也能很好的支持B/S架构,为软件设计提供了一组原则和约束条件,但这是原则和约束的条件均不具有标准

Java核心技术 卷Ⅰ 基础知识(原书第10版)

Java核心技术系列 Java核心技术 卷Ⅰ 基础知识 (原书第10版) Core Java Volume I-Fundamentals (10th Edition) [美] 凯S.霍斯特曼(Cay S. Horstmann) 著 周立新 陈 波 叶乃文 邝劲筠 杜永萍 译 图书在版编目(CIP)数据 Java核心技术 卷Ⅰ 基础知识(原书第10版) / (美)凯S. 霍斯特曼(Cay S. Horstmann)著:周立新等译. -北京:机械工业出版社,2016.8 (Java核心技术系列) 书