01 复合判断条件下,怎么写快排

//  快速排序
//  严老太太的教科书中关于快速排序是这样写的(下例为升序排序)

void quicksort(int nums[], int low, int high)
{
	if (low >= high)
		return;

	int first = low;
	int last = high;
	int key = nums[first];

	while (first < last)
	{
		while (first < last && nums[last] >= key)
			last--;
		nums[first] = nums[last];
		while (first < last && nums[first] <= key)
			first++;
		nums[last] = nums[first];
	}

	nums[first] = key;
	quicksort(nums, low, first - 1);
	quicksort(nums, first + 1, high);
}
//  算法刚开始的时候,将数组中的第一个元素用key保存了起来
//  再接着往下看
//  
//  注意看第15行与18行的while循环的循环条件,以15行为例
//  这代表的意思是,在 first < last 且 nums[last]>=key 的条件下,该循环会执行
//  执行的后果就是,last最终停留的位置将满足以下两个条件之一
//      1:last == first
//      2:last > first 且 last所标示的数组元素的值,是一个小于key的值
//  
//  如果是第一种情况,17实际上什么也不做,因为这时last == first
//  如果是第二种情况,17所干的事就是:将last所标示的那个数组元素扔到左边去!
//  暂时不管第1种情况,单论第二种状况的话,在外层循环的第一轮,16行执行结束后的状态是:
//      nums[last]的值已经被扔到左边nums[first]的位置保存了起来,即nums[first]的值遭到覆写
//      不过不要担心,之前旧的nums[first]值被key保存了起来
//      当前,nums[last]的值被保存了两次,一次是在当前的last位,一次是在当前的first位
//      即数组中保存的数值比之前小了一个,举个例子,就是容量为20的数组保存了19个数据,其中有一个数据保存了两份,缺失的那个数据被暂存在key中
//
//  接下来以同样的思路看18行的while循环,当20行执行结束后,状态是:
//      first将标示一个大于key的值,这时first左边的所有值都小于key,而first当前标示的这个值则被扔到右边nums[last]的位置保存了起来
//      nums[last]的值遭到覆写,不过不用担心,上面说过了,被覆写的值之前被保存了两次
//
//  这样内层的两个循环,除也互相扔值之外,还有下面的功效
//      15行的循环被执行,会造成last标示的值被存储了两次
//      18行的循环被执行,会造成first标志的值被存储了两次
//
//  这样第一轮外层循环结束之后
//      first左边全是 <=key 的值,但nums[first]本身 > key
//      last右边全是 >=key的值,包括nums[last]本身也 >=key
//      first与last之中,某个人标示的值被存储了两次
//
//  第二轮外层循环结束之后,数组的状态描述起来与上面的状态相同,不同的是,first与last之间的距离在缩小
//  当外层循环执行多次之后,达到 first == last 的状态时,将跳出外层循环,这时我们用pos来称呼当前first与last的共同标示位置
//  那么这时数组的状态是这样的:
//      pos左边,全是 <= key的值
//      pos右边,全是 >= key的值
//      pos本身所标示的值,已被其左边或右边某个位置备份
//
//  这个时候,执行第23行,将最早存储在Key中的值覆写到pos位置,这样,缺失的数据被保存回来了
//  
//  从27~64行之间的注释,描述了快速排序递归中的第一层所做的事情
//  就是以数组中第一个元素为标杆,将所有小于它的元素挪到它左边,将所有大于它的元素挪到它右边
//  这样,递归的对它左边及右边的数组做同样的操作,直到某层递归过程中,数组的容量为1
//  注意看函数形参列表中的low与high,当数组容量为1时,low == high,递归将解除
//
//  之上我们描述了单一的判断条件下的快速排序,现在我们设想一个这样的问题
//  我们给如下的一个结构体数组进行排序,数组中的元素代表学生

typedef struct{
    int sno;    // 学号
    int score;  // 成绩
} STUDENT;

//  排序的要求是,按成绩升序排列,当成绩相同时,按学号降序排列
//  这种情况下,我们如何改写快速排序的算法呢?
//
//  显然,麻烦来源于【当成绩相同时。。。。】
//  这个时候,我们再来看15行与18行的循环条件
		while (first < last && nums[last] >= key)   // 15
			last--;
		nums[first] = nums[last];
		while (first < last && nums[first] <= key)  // 18
			first++;
		nums[last] = nums[first];
//  我们明白15行的循环及其之后的【nums[first] = nums[last];】语句一起,总功效是将某个元素【往左扔】
//  而对应的18行的循环的功效则是【往右扔】
//  什么样的元素能往左扔?什么样的元素能往右扔呢?我们加一点小小的改动
		while (first < last && !(nums[last] < key))   // 15
			last--;
		nums[first] = nums[last];
		while (first < last && !(nums[first] > key))  // 18
			first++;
		nums[last] = nums[first];
//  这样,左扔的条件是:  在first<last的前提下 !(nums[last] < key)
//  右扔的条件是:        在first<last的前提下 !(nums[last] > key)
//  这样逻辑上看起来是不是很舒服?
//
//  这时再来分析,在STUDENT[]中,谁该向左扔,谁该向右扔
//  用语言来描述的话,左扔的条件是:
//      1:分数小于key,要左扔
//      2:当分数等于key时,若学号大于key,要左扔
//  条件1与条件2是或的关系,即满足其中任何一个即可,而且两个条件是明显互斥的
//  再用代码描述一下左扔的条件
        (stu[last].score < key.score) || (stu[last].score == key.score && stu[last].sno > key.sno)
//
//  这样,照猫画虎,可以写出STUDENT[]快速排序的第15行与18行循环条件
		while (first < last && !( (stu[last].score < key.score)||(stu[last].score == key.score && stu[last].sno > key.sno) ) )  // 15
			last--;
		stu[first] = stu[last];
		while (first < last && !( (stu[last].score > key.score)||(stu[last].score == key.score && stu[last].sno < key.sno) ) )  // 18
			first++;
		stu[last] = stu[first];
//  这样,复合判断条件下的快速排序算法就这样写出来了
//
//  当排序的判断条件是复合条件的时候,要写快速排序,就要搞明白两个重大的问题
//      1:满足什么样的条件的元素,要被往左扔
//      2:满足什么样的条件的元素,要被往右扔
//  
//  这两个问题搞明白了,写排序算法的时候,脑子就不乱
//  学习数据结构与算法,要一步一步来,慢慢来,不要怕想的过程慢,不要怕思考的过程出错
//  怕的是:思考的逻辑过程连贯,很多程序中的判断条件按直觉写,这样写出来的程序一跑就错,一测就挂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章