整理 - 排序算法

简单选择排序(蛮力法 )

void SelectSort(int a[], int n) 
{
	int i, j, t;
	for(i = 0; i < n - 1; i++)
	{
		int maxindex = i;
		for(j = 1 + i; j < n; j++)
			if(a[maxindex] < a[j])
				maxindex = j;
				
		if(maxindex != i)
		{
			t = a[maxindex];
			a[maxindex] = a[i];
			a[i] = t; 			
		}
	}
}		

简单选择排序(递归)

  • 递归函数:
    f(a, n, i)用于在无序区 a[i … n-1] (共n-i个元素)中选择最小元素放到a[i]处,是 “大问题” ,f(a, n, i+1)用于在无序区 a[i+1 … n-1] (共n-i-1个元素)中选择最小元素放到a[i+1]处,是 “小问题”

  • 递归模型:

f(a, n ,i)
{
	if (i == n-1)
		算法结束;
	else 
	{
		通过简单比较挑选a[i ... n-1]中的最小元素;
		最小元素a[k]放到a[i];
		f(a, n, i+1);
	}
}
  • 算法实现:
#include<cstdio>
#include<iostream>
using namespace std;
void swap(int &x, int &y) //参数为引用类型 
{
	int temp = x;
	x = y;
	y = temp;
}
void SelectSort(int a[], int n, int i)
{
	if (i == n-1)
		return;
	else 
	{
		//1、通过简单比较挑选a[i ... n-1]中的最小元素; 
		int k = i;	//初始化k为[i ... n-1]中最小元素所在索引
		for (int j = i+1; j < n; j++)
			if (a[j] < a[k])
				k = j;
				
		//2、最小元素a[k]放到a[i]处; 
		if (k != i)
			swap(a[i], a[k]);
			
		//3、f(a, n, i+1);
		SelectSort(a, n, i+1);
	}	
}
void display(int a[], int n)
{
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}
int main()
{
	int n = 10;
	int a[] = {2,5,1,7,10,6,9,4,3,8};
	cout << "排序前:";
	display(a, n);
	SelectSort(a, n, 0);
	cout << "排序后:";
	display(a, n);
	return 0;
}

冒泡排序(蛮力法)

void BubbleSort(int a[], int n)
{
	int i, j, t;
	for(i = 0; i < n - 1; i++)
		for(j = 1 + i; j < n; j++)
			if(a[i] > a[j])
			{
				t = a[i];
				a[i] = a[j];
				a[j] = t;
			}
}

冒泡排序(递归)

  • 递归函数:
    f(a, n, i)用于在无序区 a[i … n-1] (共n-i个元素)中找出小元素放到a[i]处,是 “大问题” ,f(a, n, i+1)用于在无序区 a[i+1 … n-1] (共n-i-1个元素)中找出小元素放到a[i+1]处,是 “小问题”

  • 递归模型:

f(a, n ,i)
{
	if (i == n-1)
		算法结束;
	else 
	{
		对a[i ... n-1]中的元素序列从a[n-1]开始进行相邻元素的比较;
		若相邻两元素反序则将两者交换(设有交换发生标志,若发生过两元素的交换,则该标志的值为true);//设置“交换发生标志”的原因:如果没有发生交换,说明后面的元素都是有序的了,所以程序可以直接结束了(一层层返回回去——退栈)
		若交换标志为false,则return; 否则,f(a, n, i+1);
	}
}
  • 算法实现:
#include<cstdio>
#include<iostream>
using namespace std;
void swap(int &x, int &y) //参数为引用类型 
{
	int temp = x;
	x = y;
	y = temp;
}
void BubbleSort(int a[], int n, int i)
{
	if (i == n-1)
		return;
	else 
	{
		//1、通过交换方式将无序区中的最小元素放置在a[i]处
		bool exchange = false;
		for (int j = n-1; j > i; j--) //从后往前两两进行比较,最终把最小的排放在a[i]位置上
			if (a[j] < a[j-1])
			{
				swap(a[j], a[j-1]);
				exchange = true;
			}
		
		//2、判断交换标志的值,从而确定是否继续递归
		if (!exchange)
			return;
		else
			BubbleSort(a, n, i+1);
	}
}
void display(int a[], int n)
{
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}
int main()
{
	int n = 10;
	int a[] = {2,5,1,7,10,6,9,4,3,8};
	cout << "排序前:";
	display(a, n);
	BubbleSort(a, n, 0);
	cout << "排序后:";
	display(a, n);
	return 0;
}

递归的简单选择排序与递归的冒泡排序的区别仅在与递归算法中“把无序区的最小元素放置a[i]处”选用的方法不同,前者使用简单比较的方式,后者使用交换方式。

快速排序(分治)

  • 二分法思想:每次归位一个元素,将整个无序序列一分为二,对这两个子序列分别再次采用同样的方式进行归并、一分为二(递归),直至子序列的长度为1或0为止。
  • QuickSort()为递归函数,递归地“将序列一为二,并进行归(实现“比基准大的都在基准的右边,比基准小的都在基准的左边”这一目标 P86图3.2)”,不过它又被封装成了Partition()函数。
  • 其实主要就是实现“将序列一分为二,并进行归并-排序”,也就是Partition()函数。我对它的流程有点疑惑,不是很能理解,但自己举例按照程序的算法走了一遍,发现是这个样子的!很流畅!就是把比基准大的移到后面,把基准小的移到前面,最后把基准元素放到正确的位置,然后返回这个位置(方便再次对基准两侧的无序序列进行排序)。
#include<cstdio>
#include<iostream>
using namespace std;
int Partition(int a[], int s, int t)
{
	int i=s, j = t, tmp = a[s];//无序序列中的第一个元素作为基准tmp 
	
	//从序列两端交替向中间扫描,直到i=j为止
	while (i != j)
	{
		//从右向左扫描,扫描到比基准小的一个数时,将a[j]前移到a[i]的位置 
		while (j > i && a[j] >= tmp)  
			j--;	
		a[i] = a[j];
		//从左向右扫描,扫描到比基准大的一个数时,将a[i]后移到a[j]的位置
		while (j > i && a[i] <= tmp)
			i++; 
		a[j] = a[i];
	} 
	a[i] = tmp;
	return i;
}
void QuickSort(int a[], int s, int t) //对a[s..t]元素序列进行递增排序 
{
	if (s < t)						  //序列内至少存在两个元素 
	{
		int i =  Partition(a, s, t);  //划分并归并,返回归并后的基准元素的位置
		
		QuickSort(a, s, i-1); //继续对归并后的基准元素两侧的子序列进行排序 
		QuickSort(a, i+1, t); //继续对归并后的基准元素两侧的子序列进行排序
	}
}
void display(int a[], int n)
{
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}
int main()
{
	int n = 10;
	int a[] = {2,5,1,7,10,6,9,4,3,8};
	cout << "排序前:";
	display(a, n);
	QuickSort(a, 0, n-1);
	cout << "排序后:";
	display(a, n);
	return 0;
}

归并排序(分治)

自底向上的二路归并排序

  • 从每个无序序列长度为1开始,两两相邻无序序列进行归并(P89图3.4),这即自底向上的二路归并排序,它由MergeSort()函数实现。但是MergeSort()函数并不是直接把这个功能实现出来,而是又将这一功能进行更小子功能的封装,由其他的函数再一步步实现。
  • 首先,MergeSort()函数把上面那一功能的实现扔给了MergePass()函数来实现趟的所有无序序列的两两归并,MergePass()函数又扔给Merge()函数实现趟中的次的两两归并。也就是说,MergeSort()函数需要实现多趟两两归并(每一趟无序序列的长度分别为length, 2length, 4length…),所以就让MergePass()函数来帮助自己实现一趟的两两归并;MergeSort()函数虽然只需要实现一趟的两两归并,但一趟中的两两归并又要执行很多次,于是MergeSort()函数让Merge()函数来帮助自己实现一次的两两归并操作。。。
  • 然后还需要理解的是,“MergeSort()函数需要几次MergePass()函数的帮助?” “MergePass()函数需要几次Merge()函数的帮助?”。也就是确定for循环的次数,也就是思考for循环怎么写。
  • 对于第一个问题,答案很容易知道,前面也已经说了“每一趟无序序列的长度分别为length, 2length, 4length…”,所以for循环这样写:
	for (length = 1; length < n; length = 2 * length)
		MergePass(a, length, n);
  • 对于后面这个问题,看笔记上的,已解释清楚。
#include<cstdio>
#include<stdlib.h> 
#include<iostream>
using namespace std;
void Merge(int a[], int low, int mid, int high)
/*对a[low, mid]、a[mid+1, high]这两个相邻序的有序列进行归并*/
{
	int *tmpa;	//用于保存a[low, mid]、a[mid+1, high]归并后的结果 
	int i = low, j = mid+1, k = 0;
	/*
		i:遍历第一个子表
		j:遍历第二个子表
		k:作为tmpa的索引 
	*/
	tmpa = (int *)malloc((high-low+1) * sizeof(int));
	
	//在第一个子表和第二个子表均未扫描完时循环 
	while (i <= mid && j <= high)
		if (a[i] <= a[j]) //tmpa[k]保存较小的一个元素 
		{
			tmpa[k] = a[i]; 
			i++; k++;
		}
		else 
		{
			tmpa[k] = a[j];
			j++; k++;
		}
	
	//判断是否在某个子表还未扫描完时,另一个子表就已经扫描完了
	//出现这样的情况的话,需要把未扫描完的子表的未扫描的元素直接添加到tmpa数组中
	while (i <= mid)
	{
		tmpa[k] = a[i];
		k++; i++;	
	} 
	while (j <= high)
	{
		tmpa[k] = a[j];
		k++; j++;
	}
	
	//将tmpa复制回a中
	for (k = 0, i = low; i <= high; i++, k++)
		a[i] = tmpa[k];
	free(tmpa); 
}
void MergePass(int a[], int length, int n)//归并长度为length时,所有无序列表中的两两相邻子表
{
	int i;
	
	for (i = 0; i+2*length-1 < n; i = i + 2*length)
		Merge(a, i, i+length-1, i+2*length-1);
		/*
			i为当前两两归并的第一个无序序列中的第一个元素
			i+length-1为当前两两归并的第一个无序序列中的最后一个元素 
			i+2*length-1当前两两归并的第二个无序序列中的最后一个元素
		*/
		
	if(i+length-1 < n)//用于最后一趟时,对余下的两个长度不相等(后者的长度小于最后一趟时length的值)的子表进行归并 
		Merge(a, i, i+length-1, n-1);
}
void MergeSort(int a[], int n) //二路归并算法 
{
	int length;
	for (length = 1; length < n; length = 2 * length) 
		MergePass(a, length, n);
}
void display(int a[], int n)
{
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}
int main()
{
	int n = 10;
	int a[] = {2,5,1,7,10,6,9,4,3,8};
	cout << "排序前:";
	display(a, n);
	MergeSort(a, n);
	cout << "排序后:";
	display(a, n);
	return 0;
}

思想像胡须,不成熟就不可能长出来。——伏尔泰
我可以理解为我的这道题的“胡须”长出来了吗。

自顶向下的二路归并排序

  • 这是典型的二分法算法:将当前序列一分为二,对这两个子序列再分别一分为二(递归),直至子序列的长度为1或0(因为一个元素的子表或者空表可以看成有序表);对上面每次得到的子序列进行排序,最后一轮的子序列的排序结果一层层返回,得到更多元素的排序,最终使得所有元素都参与了排序。(P91图3.5)
  • 不再需要MergePass()函数。只需要MergeSort()、Merge()函数。这就是利用到递归技术的好处——自动回退!且Merge()函数不需要任何改变,只是把MergeSort()函数变成了递归。
#include<cstdio>
#include<stdlib.h> 
#include<iostream>
using namespace std;
void Merge(int a[], int low, int mid, int high)
/*对a[low, mid]、a[mid+1, high]这两个相邻序的有序列进行归并*/
{
	int *tmpa;	//用于保存a[low, mid]、a[mid+1, high]归并后的结果 
	int i = low, j = mid+1, k = 0;
	/*
		i:遍历第一个子表
		j:遍历第二个子表
		k:作为tmpa的索引 
	*/
	tmpa = (int *)malloc((high-low+1) * sizeof(int));
	
	//在第一个子表和第二个子表均未扫描完时循环 
	while (i <= mid && j <= high)
		if (a[i] <= a[j]) //tmpa[k]保存较小的一个元素 
		{
			tmpa[k] = a[i]; 
			i++; k++;
		}
		else 
		{
			tmpa[k] = a[j];
			j++; k++;
		}
	
	//判断是否在某个子表还未扫描完时,另一个子表就已经扫描完了
	//出现这样的情况的话,需要把未扫描完的子表的未扫描的元素直接添加到tmpa数组中
	while (i <= mid)
	{
		tmpa[k] = a[i];
		k++; i++;	
	} 
	while (j <= high)
	{
		tmpa[k] = a[j];
		k++; j++;
	}
	
	//将tmpa复制回a中
	for (k = 0, i = low; i <= high; i++, k++)
		a[i] = tmpa[k];
	free(tmpa); 
}
void MergeSort(int a[], int low, int high) //二路归并算法 
{
	int mid;
	if (low < high)
	{
		mid = (low + high) / 2;
		MergeSort(a, low, mid);
		MergeSort(a, mid+1, high);
		Merge(a, low, mid, high);
	}
}
void display(int a[], int n)
{
	for (int i = 0; i < n; i++)
		cout << a[i] << " ";
	cout << endl;
}
int main()
{
	int n = 10;
	int a[] = {2,5,1,7,10,6,9,4,3,8};
	cout << "排序前:";
	display(a, n);
	MergeSort(a, 0, n-1);
	cout << "排序后:";
	display(a, n);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值