[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 指向函数的指针,用于肯定排序的顺序

node

        整数快排
        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); windows

#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--; 数组变为:
less


        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/