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

首先简单谈下快速排序的特点,时间复杂度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;
}

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

时间: 2024-08-02 02:49:30

算法整理(二)---快速排序的两种实现方式:双边扫描和单边扫描的相关文章

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

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

二、C++迭代器的两种实现方式 (Range for和C#、Java中的foreach)

一.迭代器概述 这个标题其实有点"标题党"的含义,因为C++在标准库中的实现迭代器的方式只有一种,也就是为类定义begin()和end()函数,C++11增加了range for语句,可以用来遍历迭代器中的元素.实现迭代器的第二种方式,就是用C++模拟C#和Java中的迭代器模式,并且我们可以定义出自己的foreach语句.除此之外,迭代器可能还有很多种实现的方法,各个库也会多自己的迭代器的实现有所定义,在这里只要明白迭代器的本质意义即可. 迭代器,也称作游标,是一种设计模式,我们可以

Unity3d Android SDK接入解析(二)Unity3d Android SDK的设计与两种接入方式

一.前言 上篇说清楚了Unity和Android调用的方式,但很多实际接入的部分没有讲的很详细,因为重头在这篇,会详细讲述具体接入Android SDK的方式,和怎么去做一个方便Unity接入的SDK. 传送门: 前篇:Unity3d 与 Android之间的互相调用 http://blog.csdn.net/yang8456211/article/details/51331358 后篇:Unity3d Android SDK接入解析(三)接入Android Library的理解 http://

[转]Web APi之认证(Authentication)两种实现方式【二】(十三)

本文转自:http://www.cnblogs.com/CreateMyself/p/4857799.html 前言 上一节我们详细讲解了认证及其基本信息,这一节我们通过两种不同方式来实现认证,并且分析如何合理的利用这两种方式,文中涉及到的基础知识,请参看上一篇文中,就不再叙述废话. 序言 对于所谓的认证说到底就是安全问题,在Web API中有多种方式来实现安全,[accepted]方式来处理基于IIS的安全(通过上节提到的WindowsIdentity依赖于HttpContext和IIS认证)

[算法模版]Tarjan爷爷的两种图论算法

[算法模版]Tarjan爷爷的两种图论算法 前言 Tarjan爷爷发明了很多图论算法,这些图论算法有很多相似之处(其中一个就是我都不会).这里会对这三种算法进行简单介绍. 定义 强连通(strongly connected): 在一个有向图\(G\)里,设两个点$ a, b \(发现,由\)a\(有一条路可以走到\)b\(,由\)b\(又有一条路可以走到\)a\(,我们就叫这两个顶点\)(a,b)$强连通. 强连通图: 如果 在一个有向图\(G\)中,每两个点都强连通,我们就叫这个图,强连通图.

Web API之认证(Authentication)两种实现方式【二】(十三)

前言 上一节我们详细讲解了认证及其基本信息,这一节我们通过两种不同方式来实现认证,并且分析如何合理的利用这两种方式,文中涉及到的基础知识,请参看上一篇文中,就不再废叙述废话. 序言 对于所谓的认证说到底就是安全问题,在Web API中有多种方式来实现安全,[accepted]方式来处理基于IIS的安全(通过上节提到的WindowsIdentity依赖于HttpContext和IIS认证)或者在Web API里通过使用Web API中的消息处理机制,但是如果我们想应用程序运行在IIS之外此时Win

二分查找的两种实现方式

笔者在这里给出二分查找的两种实现方式. 一. 第一种是健忘版的二分查找,即不管是否已经找到target,查找算法都继续对表进行再分,直到剩下的表的长度为1. 递归实现如下: Error_code recursive_binary_1(const Ordered_list &the_list, const Key &target, int bottom, int top, int &position) { Record data; if (bottom < top) { int

PlaceHolder的两种实现方式

placeholder属性是HTML5 中为input添加的.在input上提供一个占位符,文字形式展示输入字段预期值的提示信息(hint),该字段会在输入为空时显示. 如 1 <input type="text" name="loginName" placeholder="邮箱/手机号/QQ号"> 目前浏览器的支持情况 浏览器 IE6/7/8/9 IE10+ Firefox Chrome Safari  是否支持 NO YES YE

冒泡排序及两种优化方式

冒泡排序是最常用的小型数据排序方式,下面是用C语言实现的,及其两种优化方式. 第一种优化方式是设置一个标记位来标记是否发生了交换,如果没有发生交换就提前结束: 第二种优化方式是记录最后放生交换的位置,作为下一趟比较结束的位置. #include <stdio.h> /* * 打印数组 * */ void printArray(int arr[], int n) { int i = 0; for (i = 0; i < n; ++i) { printf("%d ", a