(2011.12.03) 07_归并排序(mergesort).cpp

// 07_归并排序(mergesort).cpp

/**
 * -> mergesort
 * 1. 归并排序(mergesort)体现的是分治的策略,先分,再治。(divide-and-conquer)
 * 2. 将一个数组分割成多个数组,然后再将这些数组的元素一个个提取出来,比较,合并放入新的数组当中。
 * 3. 先分成最小的子数组,再逐步扩大,再另外分出小的子数组,再扩大,再将两个相对有序的子数组合并,不断循环。
 **/


#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 mergesort(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;
		// 排序
		mergesort(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;
} 


/**
 * 子程序名称:mergesort
 * 子程序返回类型:void
 * 子程序入口参数:vector<double> &
 * 子程序功能:将vector<double>内部从小到大的顺序排序。
 **/

// 归并排序的程序入口函数,此处的形参只有一个,就是需要排序的vector向量
void mergesort(vector<double> & test)
{
	// 先建立一个跟调入数组同样大小的数组,以方便以后的工作,这个数组的功能是临时存储旧的元素
	vector<double> temptest(test.size());
	// 函数声明,此处为mergesort的重载函数,参数比这函数多了三个,一个是刚刚所建立的临时数组
	// left是子数组的最左下标,right是子数组的最右下标
	void mergesort(vector<double> & test, vector<double> & temptest, int left, int right);
	mergesort(test, temptest, 0, static_cast<int>(test.size() - 1));
	return;
}

// 归并排序的程序排序方法控制 -- 两路归并的递归算法
// 入口参数有四:1.源数组, 2. 临时数组, 3. 源数组的最左下标, 4. 源数组的最右下标
void mergesort(vector<double> & test, vector<double> & temptest, int left, int right)
{
	// 函数声明,实现一个分割成两个子数组的重新排序merge排序的实现的函数
	void merge(vector<double> & test, vector<double> & temptest, int left, int rightstart, int rightend);

		// 使用两路归并递归,就是将数组分割成很多子数组,停止递归的条件就是当left >= right的时候
		// 这里递归比较的顺序(程序执行顺序)是:假始初始化了一个大小为12的数组
		// 第01次:0 1
		// 第02次:0   2
		// 第03次:      3 4
		// 第04次:      3   5
		// 第05次:0         5
		// 第06次:            6 7
		// 第07次:             6   8
		// 第08次:                  9 10
		// 第09次:                  9    11
		// 第10次:            6          11
		// 第12次:0                      11
		
		// 为什么程序会有这样的执行顺序?为什么要这样使用递归?现在的递归的思想是怎样的?
		// 这时,想了很久,看了很久这个递归的方法到底有些什么规律,后来才发觉,
		// 假如将下面的mergesort都看成二叉树的访问,二叉树的后序遍历是这样的:
		// void printall(BinTreeNode * subTree)
		// {
		//   if(subTree != NULL)
		//    {
		//       printall(subTree -> leftchild);	// 后序遍历subTree的左子树
		//       printall(subTree -> rightchild);	// 后序遍历subTree的右子树
		//       cout << subTree -> data;			// 访问结点的数据域
		//     }
		//   return;
		//  }
		// 可以看出,这种后序遍历法,跟下面的二路归并的思路非常相似,这样就可以解释为什么二路归并要这么使用递归
		// 但是,现在问题是,怎么就可以将一个带有三个参数的函数看成一棵树呢?
		// 我们观察上面遍历二叉树使用递归时候的特点可以知道,
		// 在每次递归,它使用了一个判断条件,subTree是否为NULL来作为基准条件,判断程序是否往下执行
		// 而对本程序的递归,它的基准条件其实是一样的,原来是:if ( left < right),
	    // 可以转化得: 0 < right - left,而又因为下面还有一个center的概念,所以可以知道,我们需要再加一个条件
	    // 将原来的基准条件转化为: 0 < ((right - left) / 2 )即  0 < center
		// 所以,其实,center > 0 就是这个循环的条件,也就是说,这棵树的其中一个最重要形成因素是带center的规律
	    // 根据程序运行顺序得出下面的二叉树,
	    // ............................(0~11[5.5])............................
	    // ...........(0~5[2.5]).........................(6~11[8.5])..........
	    // ....(0~2[1]).......(3~5[4])...........(6~8[7])......(9~11[10]).....
	    // .(0~1[0.5])...(3~4[3.5])...........(6~7[6.5]).....(9~10[9.5])......
	    // 这时,二路,也可以去看成二叉树的左子树,右子树了。
        // 因此,使用后序遍历的方法,就可以最后将0-11的元素排好序.
	if (left < right)
	{
		int center = (left + right) / 2;
//		cout << endl << "a :left: " << left << "\tright: " << right << "\tcenter: " << center << endl;
		mergesort(test, temptest, left, center);
		cout << endl << "b :left: " << left << "\tright: " << right << "\tcenter: " << center << endl;
		mergesort(test, temptest, center + 1, right);
//		cout << endl << "c :left: " << left << "\tright: " << right << "\tcenter: " << center << endl;
		merge(test, temptest, left, center + 1, right);
//		cout << endl << "d :left: " << left << "\tcenter: " << center << "\tright: " << right << endl;
	}
	return;
}

// void merge() 归并排序的归并功能实现
// 入口参数有四:1.源数组, 2. 临时存储数组, 3. 源数组的最左下标, 4. 源数组的分割线右侧,5.源数组的最右下标
void merge(vector<double> & test, vector<double> & testtemp, int left, int rightstart, int rightend)
{
	// 在程序开始前,需要先知道这个函数实现这些功能需要使用哪些参数,
	// 或者说,这时候假如相当于定义一个类,要知道这个类需要用到哪些数据成员
	// 要想实现两子数组的合并,最初需要知道,A数组的左右边界,B数组的左右边界
	// 现在,已经在函数调入进来时候,直接得到了A数组的左边界-left, 还有B数组的左右边界-rightstart,rightend
	// ->也就是说,还差一个leftend,A数组的右边界,(下面定义leftend = rightstart - 1)
	// 另外,还需要一个定位的下标,因为实现该功能的时候还用到了一个临时存储数组,而这个数组的下标还是需要另外定义的,
	// 当从第一个子数组与第二个子数组选择输入数据到这个临时存储数组时,如果不申请多一个变量用于下标定位,
	// 很难知道这个数组的现在放元素放到哪个位置了,此时,temppos就出现了,因为数组这时需要定义从左往右存放数据,
	// ->所以,也设temppos的开始位置是源数组的left
	// 最后,因为知道我们定义的这个临时存储数组的作用只是临时把比较出来,排好序的数据放入到这里,所以,还需要将它复制出源数组
	// ->此时,在一开始,我们就获取这个源数组进来时候的大小,以确定要复制数据到源数组的循环次数

	int leftend = rightstart - 1;					// leftend为第一个子数组的左边界
	int temppos = left;
	int arrsize = rightend - left + 1;

	// 当两数组都不为空时,抽出元素,两两比较,将原来相对有序的子数组中的元素放入新数组中
	while (left <= leftend && rightstart <= rightend)
	{
		if (test[left] < test[rightstart])
		{
			testtemp[temppos++] = test[left++];
		}
		else
		{
			testtemp[temppos++] = test[rightstart++];
		}
	}
	// 当其中一个子数组还未清空时,直接将那些数据放入到需要存放数据的数组中去
	while (left <= leftend)
	{
		testtemp[temppos++] = test[left++];
	}
	while (rightstart <= rightend)
	{
		testtemp[temppos++] = test[rightstart++];
	}
	// 最后,将临时存放数据的数组中的元素放入到源数组中去,确定数组大小方法是用到之前定义的arrsize,
	// 而确认起始点的方法是利用源数组的rightend,一直递减。。。
	for ( int i = 0; i < arrsize; ++i)
	{
		test[rightend] = testtemp[rightend];
		--rightend;
	}
	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;
}

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值