(2011.12.01) 06_快速排序(QuickSort).cpp

 
// 06_快速排序(QuickSort).cpp

/**
 * -> quicksort
 * 1. 快速排序是内排序中的一种交换排序的方法。
 * 2. 快速排序使用了一种经典的分治思想。
 * 3. 快速排序是传说中最快的一种已知的排序算法,它的平均运行时间是O(NlongN)
 * 4. 快速排序最基本的思想:
 *        01. 通过选取一个pivot枢纽元,
 *        02. 以pivot为基准将数组中的元素划分为两个子数组
 *        03. 这两个子数组的特征是,像一棵二叉树一样,一棵是存放大于pivot的元素,另一棵则存放小于pivot的元素
 *        04. 通过不断地这样循环选取pivot,不断地划分二叉树,最后会形成一个有序的数组(相当于中序遍历这棵二叉树)
 * 5. 其中汲及到的问题有:
 *         01. 如何选取pivot枢纽元(三数中值分割法)
 *         02. 如何划分子数组(i,j 右左移动划分子数组)
 **/


#include <iostream>
#include <vector>
#include <conio.h>		// _getch();
					
using std::cin;				// using 声明
using std::cout;
using std::endl;
using std::vector;


// ________________________ 主函数 _______________________________
int main()
{
	void InsertArr(vector<double> & test);
	void quicksort(vector<double> & test);
	void ShowArr(vector <double> & test);
	bool testagain(true);
	char testagainjudge;
	vector<double> testArr;				// 用于测试的数组
	do
	{
		cout << "------------------------- 现在开始数组的插入排序测试 ---------------------------\n";
		cout << " -> 说明:该测试共分为三个步骤:输入 -> (系统内部)排序 -> 输出显示.\n"
                << "-> 注意:在输入时,请按任意字母结束输入。\n";
		// 插入
		InsertArr(testArr);
		ShowArr(testArr);
		cout << endl;
		// 排序
		quicksort(testArr);
		ShowArr(testArr);
		cout << endl;

		cout << "-> 如需重新测试,请按字符'a',否则请按任意键退出...";
		testagainjudge = _getch();
		if (testagainjudge == 'a')
		{
			cin.sync();
			testArr.clear();
			testagain = true;
			system("cls");
		}
		else 
		{
			testagain = false;
		}
	}while (testagain);
	return 0;
}

/**
 * 子程序名称:InsertArr
 * 子程序返回类型:void
 * 子程序入口参数:vector<double> &
 * 子程序功能:由用户设定N个数值,并由用户输入这N个数值,程序将其保存入vector<double>入口参数处。
 **/
void InsertArr(vector<double> & test)
{
	cout << "-> 请输入需要输入的数值个数:";
    unsigned int n;
    cin >> n;
    cout << "-> 现在开始数值输入(请以空格间开):";
    for ( unsigned int i = 0; i < n; ++i)
    {
		double value; 
        cin >> value;
        while(cin.fail())
        {
          cin.sync();
          cin.clear();
          cin >> value;
        }
        test.push_back(value);
     }
     cout << "-> 输入操作完成.\n";
    return;
} 

/**
 * 子程序名称:swap
 * 子程序返回类型: double &
 * 子程序入口参数:double & a, double & b ,调用两个double型的引用值
 * 子程序功能: 使a与b的数值交换。(因为下面多处需要使用这一功能,所以定义这个函数)
 **/
void swap(double & a, double & b)
{
		double temp = a;
		a = b;
		b = temp;
		return;
}
	
/**
 * 子程序名称:median3
 * 子程序返回类型:double & (引用方式返回本次对比得出的pivot)
 * 子程序入口参数:vector<double> &(数组的入口指针), int left(需要用到的数组左值位置), int right(需要用到的数组右值位置)
 * 子程序功能:执行三数中值分割。
 *                         0. 比较array的[left], [right], [middle = (right + left) / 2],
 *                         1. 使最小值放在a[left], 最大值放在a[right], 次大值[middle]放在[right - 1]处
 *                         2. 令quicksort()函数中的i 初始化为[left + 1], j 初始化为 [right - 2]
 *            (优点)  3. arr[left]可以作为j的警戒标记,a[right - 1]作为i的警戒标记
 **/
double & median3 (vector<double> & test, int left, int right)
{
	int middle( (left + right) / 2);						// 求出中值的位置

	// 目标是使最小值在left,最大值在right, 要比较的参数有三个,先确定一个再确定另外两个
	// 下面的程序,先确定第一个参数, left,通过分别与另外两个参数比较,可以得出最小值放在left
	// 最后只需要将剩下两个数比较,就可以得出次大值和最大值了。
	if (test[left] > test[middle])							// left与middle比较
		swap(test[left], test[middle]);				// 较小值放在left
	if (test[left] > test[right])							// left与middle比较
		swap(test[left], test[right]);					// 较小值放在right
	if (test[right] < test[middle])						// middle与right比较
		swap(test[right], test[middle]);				// 最大值放在right
	
	swap(test[middle], test[right - 1]);						// 将pivot(枢纽元放在right - 1)处
	return test[right - 1];
}

/**
 * 子程序名称:quicksort
 * 子程序返回类型:void
 * 子程序入口参数:vector<double> &
 * 子程序功能:将vector<double>内部从小到大的顺序排序。
 **/
void quicksort(vector<double> & test)
{
    void quicksort(vector<double> & test, int left, int right);
	quicksort(test, 0, test.size() -  1);
	return;
}

void quicksort(vector<double> & test, int left, int right)
{
	if (left >= right) return;
	double pivot = median3(test, left, right);		// 设定枢纽元,使用三数中值分割的方法,将数组中三个数的值排序好
	
	// 经过上一次的函数调用以后,现 在test数组中,a[left]相对a[right],a[right - 1]为最小值,a[right]相对为最大值
	// 现在可以将这种方法的实现想像成一棵树,而pivot就是这棵树的树根,将数组中全部的数一次次的分下去,
	// 相对树根小的子树放在左儿子处,相对树根大的子树放在右儿子处,这样一层层分下去,最后得到一个排好序的数组。
	// 假如本来有数 (0)2, (1)3, (2)5, (3)21, (4)9, (5)6, (6)54, (7)7, (8)1, (9)4,调用了刚刚上面的median3函数后,
	// 数组将会 (0)2_比_, (1)3, (2)5, (3)21, (4)1_比_换_, (5)6, (6)54, (7)7, (8)4_换_, (9)9_比_, 序号为 0, 8, 4的数将会改变,按从小到大的顺序存放
	// 而这将会变成一棵树根为4的树,最后使用中序遍历这棵树,就是数组所排列出来的顺序啦。
	//           4
	//       /      \
	//    m<4      n>4
	int i = left;						// 设定 i 为左值
	int j = right -  1;				    // 设定 j 为右值 
	if (i >= j) return;
	// 先说分割目的:把所有的相对pivot枢钮元小的元素移到数组的左边,把所有的相对pivot枢钮元大的元素称移到数组的右边。
	// 然后,现在设定i和j的值,分别为左值,右值,可以理解成i经过的数就是小于pivot的数,而j经过的数就是大于pivot的数
	// 刚刚的数组是 (0)2_比_, (1)3, (2)5, (3)21, (4)1_比_换_, (5)6, (6)54, (7)7, (8)4_换_, (9)9_比_
	// 现在,将i 放在0,将j 放在right - 1处,至于为什么不将j设定为right - 2呢,这样不是更加符合与pivot的思路吗?这跟下面的算法有关
	// 现在的数组 (0)2_比_i, (1)3, (2)5, (3)21, (4)1_比_换_, (5)6, (6)54, (7)7, (8)4_换_j, (9)9_比_
	// 设定i, j 为左右值的目的:将数组分割,i一直向右移动,直至遇上大于pivot的值,(不符合源分割目标)停止移动。
	//                                                                      j 一直向左移动,直至遇上小于pivot的值,(不符合源分割目标)停止移动。
	//                                                                     当ij都停止移动后,将ij的值交换,那么,就可以将相对大小的位置放到正确的位置了。
	//                                                                    (相对小,左边,相对大,右边)

	// 接下来这个循环的目的是实现分割,通过不断地循环一个个元素的比较,交换,到最后i>=j时跳出循环
	while(i < j)							// 当ij仍符合i<j 的条件时,继续寻找下一元素
	{
		while( test[++i] < pivot){}			// 将左边小于枢钮元的元素过滤掉,找出大于pivot的元素
		while( test[--j] > pivot){}				// 将右边大于枢钮元的元素过滤掉,找出小于pivot的元素
		if (i < j)										// 当i仍小于j的时候,将i,j 位置对应的值交换
			swap(test[i], test[j]);					// 使大于pivot的元素放在j的一侧,而小于pivot的元素放在i的一侧
	}							
	// 现在用数组来模拟刚刚的过程
	// 原:(0)2_i_left,    (1)3,       (2)5,       (3)21,       (4)1,       (5)6,       (6)54,       (7)7,       (8)4_j_right-1,   (9)9_right
	// 寻找大于4的i, 小于4的j
	// next: (0)2_left,      (1)3,       (2)5_i,    (3)21,       (4)1_j,    (5)6,       (6)54,       (7)7,       (8)4_right-1,       (9)9_right    
	// 找到后,交换:
	// next: (0)2_left,      (1)3,       (2)1_i,    (3)21,       (4)5_j,    (5)6,       (6)54,       (7)7,       (8)4_right-1,       (9)9_right  
	// 交换后,继续寻找大于4的i, 小于4的j:
	// next: (0)2_left,      (1)3,       (2)1,    (3)21_i_j,     (4)5,    (5)6,       (6)54,       (7)7,       (8)4_right-1,       (9)9_right  
	// 可以看出,此时已经满足跳出条件了,当i = j 或者i>j的时候即要跳出循环,而现在,对于分割的目标,
	// 有点小缺陷,就是(3)与(8)的值,如果此时将数组看为一棵树,
	// 那么,分割线应该在中间的,很明显,现在的中线应在(3)的位置,
	// 而(3)就是i的位置,i跳出时候的位置了,所以,此时,应该对该数组调整,i与right - 1的位置交换
	swap(test[i], test[right - 1]);
	// 交换后数组: (0)2_left,      (1)3,       (2)1,    (3)4_i_j,     (4)5,    (5)6,       (6)54,       (7)7,       (8)21_right-1,       (9)9_right  
	// 实现了这一步以后,如何进行下一轮的比较分割呢?
	// 现在的问题思路还是跟刚刚开始快速排序时候一样的,首先,需要确定枢钮元pivot,然后再进行分割。
	// 而这个确定枢钮元的步骤已经在这个函数里面思考了,也就是说,我们只需要重复调用这个函数,
	// 而调用这个函数的时候,我们只需要考虑的是它的入口,它需要调入一个左边界与右边界,
	// 好吧,开始对数组进行分割,在下一次对比当中,树根已经不需要理会了,所以分割成两个子数组时,
	// 左边的子数组,左边界,依然是 left,右边界,i-1
	// 右边的子数组, 左边界,i + 1,右边界,right
	quicksort(test, left, i -1);				// 排列左数组
	quicksort(test, i + 1, right);			// 排列右数组
	return;
}


/**
 * 子程序名称:ShowArr
 * 子程序返回类型:void
 * 子程序入口参数:vector<double> &
 * 子程序功能:遍历并显示vector<double>&。
 **/
void ShowArr(vector <double> & test)
{
     cout << "-> 现在开始显示确认刚刚所输入的数组顺序:\n";
     cout << "-> ";
     vector<double>::const_iterator be(test.begin());
     vector<double>::const_iterator en(test.end());
     while ( be != en)
     {
           cout << *be++ << " ";
	 }
      return;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值