• 算法整理(二)---高速排序的两种实现方式:双边扫描和单边扫描


    首先简单谈下高速排序的特点,时间复杂度O(nLog n),最差时间复杂度O(n^2),平均时间O(nLog n).由于用到了函数栈,空间复杂度为O(lg n),最差为O(n).是一种不稳定的排序方法。基本思想是分治法,这位大大的http://blog.csdn.net/morewindows/article/details/6684558 讲的很清楚了,分治法+挖坑法,我就不多说了。就是以某个数为參照,使得左边的都小于他,右边的数都大于他。然后对他的左右两个区间採取相同的方法进行递归。

    就其总体实现而言,有两大种思路,一是双边扫描,二是单边扫描。以下分别来上程序:

    一、双边扫描

    双边扫描是谭浩强书中的方法,个人认为比以下的单边扫描更好理解,也是博文里採用的方法。以下看程序:

    void quickSort1(int* x, int l, int r){
    
    	if(l < r){
    		int i = l, j = r, key = x[l];
    		while(i < j){
    			while( i < j && x[j] >= key){
    				j--;
    			}
    			if(i < j){
    				x[i++] = x[j];
    			}
    			while(i < j && x[i] <= key){
    				i++;
    			}
    			if(i < j){
    				x[j--] = x[i];
    			}
    		}
    		cout<<"i = " <<i<<" j = "<<j<<endl;
    		x[i] = key;
    		quickSort1(x, l, i-1);
    		quickSort1(x, i+1, r);
    	}
    
    }

    双边扫描很直观,首先进到程序里推断是否l<r,当满足条件才进去。这也是用递归的一个必要条件,一定要让函数有尽头,有边界。然后进入大while循环,接着进入小while循环,先从右边找,仅仅要满足数字大于key就一直让j往左移。直到第一个不满足条件的,就是第一个小于key的数跳出while循环,将它放在左边挖的“坑”上。同一时候让坑的索引+1,接着从左边開始扫描,找到第一个大于key的数,再将它填到右边的坑上。右边的坑索引-1,接着再从右边扫描。直到最后跳出大while循环,此时i = j。也就是完毕了一次高速排序的扫描。之后将最初的key放到x[i],事实上放到x[j]也是一样的。由于i等于j么,此时!然后进行递归,对区间[l, i - 1], [i+1, r]进行相同的操作。

    双边排序的要点: 1、最初的if一定要有,这是最后递归出来的标志位。2,为了找到一个数使它的左边都大于它,右边都小于它,要多次循环,这个循环就是大while循环。3、双边排序不须要swap,即无需交换。


    二、单边扫描

    上面的双边排序,出来一次大while循环,要从两边进行多次。单边扫描,则仅仅需从左走到右就能完毕一次 快排。

    void quickSort2(int x[], int l, int r){
    	if(l >= r)
    		return;
    	int m = l;
    	for(int i = l + l; i <= r; i++ ){
    		if(x[i] < x[l]){
    			swap2(x[++m], x[i]);
    		}
    	}
    	swap2(x[l], x[m]);
    	quickSort2(x, l, m - 1);
    	quickSort2(x, m + 1, r);
    
    }
    void swap2(int &a,int &b){
    	if(a==b) return;//对同一地址的数据交换,会使其结果为0
    	a=a^b;
    	b=a^b;
    	a=a^b;
    }
    代码是不是更简单了?程序先进行推断,假设l>=r直接return,这点跟双边扫描的if一个意思,都是为递归创造结束的标志。然后用m记录最左边的那个的索引,这里默认的是第一个,即x[l]的索引。[注,m的初始值不一定指向key!,仅仅是指向最左边的。]然后进入扫描,直接从l + 1開始,假设右边的小于key,就让x[++m]和x[i]交换。假设右边的大于key,则不进行不论什么操作。这里有个特例,假设l = 0, 则m = 0.假设x[1]小于x[0],则让x[1] 和x[1]进行交换,也就等于没交换。假设数组是5 4 3 2 1,则这里的交换就失效了。 再往后看,直到for循环结束,走出循环,让最后m指的位置的数和最初的key进行交换。如上面 5 4 3 2 1,则第一次快排的结果是 1 4 3 2 5,仅仅有for出来后的那次swap才起作用。这里的m有个特殊含义,即指向小于key的最右边的那个数。所以出来后才用它(x[m])和key(即x[l])进行交换。

    单边扫描的特点:

    1、程序须要交换;

    2、更有冒泡法的色彩;冒泡的目的不是让最大的数沉到最右边,而是让小于key的都左移,找到分界索引m。使之和key进行交换。

    3、此版本号的的单边扫描属于最基础的,还能够优化。

    本想測出两个算法的时间 消耗差异,遗憾的是c++获得程序执行时间太费劲了,弄半天没弄成。以下附上完整程序:

    //============================================================================
    // Name        : QuikSort.cpp
    // Author      : YanZi
    // Version     :
    // Copyright   : Your copyright notice
    // Description : Hello World in C++, Ansi-style
    //============================================================================
    
    #include <iostream>
    #include <malloc.h>
    
    using namespace std;
    void swap1(int a, int b);
    void printArray(int* in, int n);
    
    void quickSort1(int* x, int l, int r);//双边扫描,高速排序
    void quickSort2(int x[], int l, int r);//单边扫描,高速排序
    void swap2(int &a,int &b); //交换,在MinGW上必须採用此方法,swap1无效
    
    
    #define N 8 //数组的长度
    
    int main() {
    	int* input = NULL;
    	input = (int*)malloc(N * sizeof(int));
    	if(input == NULL){
    		cout<<"内存溢出"<<endl;
    	}
    	for(int i = 0; i < N; i++){
    		input[i] = rand();
    	}
    	//	int input[] = {55, 41, 59, 26, 53, 58, 97, 93};
    
    	cout<<"原始数据:"<<endl;
    	printArray(input, N);
    
    	quickSort2(input, 0, N-1);
    	printArray(input, N);
    
    	return 0;
    }
    void swap1(int a, int b){
    	int temp = a;
    	a = b;
    	b = temp;
    }
    void printArray(int * in, int n){
    	if(in == NULL){
    		return;
    	}
    	for(int i = 0; i<n; i++){
    		cout<<" "<<in[i];
    	}
    	cout<<endl;
    
    }
    
    void quickSort1(int* x, int l, int r){
    
    	if(l < r){
    		int i = l, j = r, key = x[l];
    		while(i < j){
    			while( i < j && x[j] >= key){
    				j--;
    			}
    			if(i < j){
    				x[i++] = x[j];
    			}
    			while(i < j && x[i] <= key){
    				i++;
    			}
    			if(i < j){
    				x[j--] = x[i];
    			}
    		}
    		cout<<"i = " <<i<<" j = "<<j<<endl;
    		x[i] = key;
    		quickSort1(x, l, i-1);
    		quickSort1(x, i+1, r);
    	}
    
    }
    void quickSort2(int x[], int l, int r){
    	if(l >= r)
    		return;
    	int m = l;
    	for(int i = l + l; i <= r; i++ ){
    		if(x[i] < x[l]){
    			swap2(x[++m], x[i]);
    		}
    	}
    	swap2(x[l], x[m]);
    	quickSort2(x, l, m - 1);
    	quickSort2(x, m + 1, r);
    
    }
    void swap2(int &a,int &b){
    	if(a==b) return;//对同一地址的数据交换,会使其结果为0
    	a=a^b;
    	b=a^b;
    	a=a^b;
    }




  • 相关阅读:
    rails 相关文件
    linux学习---vi进行多行的copy,cut
    设计
    互联网---现在正在变成过去
    testing
    TTl
    如何成为优秀的性能测试工程师
    linuX学习
    Programiz 中文系列教程·翻译完成
    Programiz C 语言教程·翻译完成
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/3841249.html
Copyright © 2020-2023  润新知