ONE 选择排序

  • 简单选择排序

       顾名思义,简单选择排序就是每一趟(如第i趟)从待排序列选取最小的一个元素放到第 i 个位置,直到第n-1趟做完。

//简单选择排序
void SelectSort(vector<int> &buf)
{
	int n = buf.size();
	for (int i = 0; i < n - 1; i++)
	{
		int min = i;		        //记录最小元素位置
		for (int j = i + 1; j < n; j++)	//在buf[i]到buf[n-1]中选择最小元素
		{
			if (buf[j] < buf[min])	
			{
				min = j;        //更新最小元素的位置
			}
		}
		if (min != i)
		{
			swap(buf[i], buf[min]);	//与第i个位置交换
		}
	}
}
  • 堆排序

       堆排序是一种树形选择排序方法。它的特点是:在排序过程中,将L[1...n]看成一棵完全二叉树的顺序存储结构,利用二叉树中双亲结点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或者最小)的元素。

       堆的定义如下:n个关键字序列L[1...n]称为堆,当且仅当该序列满足:

  1.   L(i)<=L(2i) 且 L(i)<=L(2i+1)  
  2.   L(i)>=L(2i) 且 L(i)>=L(2i+1)

       满足 1 的堆称为小根堆,满足 2 的堆称为大根堆。

       另外,堆经常被用来实现优先级队列,优先级队列在操作系统的作业调度和其他领域有广泛应用。

       堆排序的关键是构造初始堆。对初始序列建堆,就是一个反复筛选的过程。n个结点的完全二叉树,最后一个结点是第【n/2】结点的孩子。所以,从第【n/2】结点开始,对第【n/2】结点为根的子树进行筛选(比如大根堆:若根结点的关键字小于左右子女中关键字较大者, 则交换),使该子树成为堆。之后向前依次对各个结点为根的子树进行筛选,看该结点值是否大于其左右子结点的值,若不是,将左右子结点中较大值与之交换,交换后可能破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止。反复利用上述调整堆的方法建堆,直到根结点。

       过程如图所示:

各种排序算法(二)

 

建立大根堆的算法:

//调整函数
void AdjustDown(vector<int> &buf, int k, int len)
{
	buf[0] = buf[k];	//暂存入buf[0]
	for (int i = 2 * k; i <= len; i *= 2)	//沿key较大的子节点向下筛选
	{
		if (i < len&&buf[i] < buf[i + 1])	
		{
			i++;		//取key较大的子节点的下标
		}
		if (buf[0] >= buf[i])
		{
			break;		//筛选结束
		}
		else
		{
			buf[k] = buf[i];	//将buf[i]调整到双亲结点上
			k = i;				//修改 k 值,以便继续向下筛选
		}
	}
	buf[k] = buf[0];			//被筛选节点的值放入最终位置
}
//建立大根堆
void BuildMaxHeap(vector<int> &buf,int len)		
{
	for (int i = len / 2; i > 0; i--)	//从 n/2 到 1 ,反复调整堆
	{
		AdjustDown(buf, i, len);
	}
}

       接下来是具体排序操作,首先由于堆本身性质,堆顶元素就是最大值。输出堆顶元素后(通常是把堆顶元素与堆底元素互换位置,然后使堆结点数 - 1),此时根节点已经不满足大根堆性质,继续调用调整函数使其再次满足大根堆性质,再互换堆顶元素,如此重复,直到堆中只剩下一个元素为止。

堆排序算法:

//堆排序
void HeapSort(vector<int> &buf, int len)
{
	BuildMaxHeap(buf, len);			//初始建堆
	for (int i = len; i > 1; i--)	//n-1趟的交换和建堆过程
	{
		swap(buf[i], buf[1]);		//输出堆顶元素(和堆底元素交换)
		AdjustDown(buf, 1, i - 1);	//把剩余的元素整理成为堆
	}
}

       堆排序算法的时间复杂度是O(nlog2n),空间复杂度为O(n),是一种很高效的排序算法。

 

TWO 归并排序

  • 2--路归并排序

       归并排序与前面的基于交换,选择等排序的思想不一样,“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。假定待排序表含有n个记录,则可以看做n个有序的子表,每个表长度为1,然后两两合并,得到[n/2]个长度为2或1的有序表,再两两归并,....如此重复,直到合并成为一个长度为n的有序表为止,这种排序算法称为2-路归并排序。

       首先写将两个有序表归并成为一个有序表的算法void Merge():

//合并算法
//表buf的两段buf[low...mid]和buf[mid+1...high]各自有序,将它们合并为一个有序表
vector<int> buf_copy(20);		                //辅助数组
void Merge(vector<int> &buf, int low, int mid, int high)
{
	int i, j, k;
	for (int k = low; k <= high; k++)
	{
		buf_copy[k] = buf[k];	                //将buf数组全部复制到buf_copy
	}
	for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++)
	{
		if (buf_copy[i] <= buf_copy[j])		//比较buf_copy数组左右两段中的元素
		{
			buf[k] = buf_copy[i++];		//将较小值复制到buf中
			draw(buf, 0);
		}
		else
		{
			buf[k] = buf_copy[j++];	
			draw(buf, 0);
		}
	}
	while (i <= mid)
	{
		buf[k++] = buf_copy[i++];	//若前半段未检测完,则剩余部分全部复制进buf
		draw(buf, 0);
	}
	while (j <= high)
	{
		buf[k++] = buf_copy[j++];	//若后半段未检测完,则剩余部分全部复制进buf
		draw(buf, 0);
	}
}

       最后是归并排序的具体代码,其效果是反复递归,使得原始序列被不断从中间分割,直到成为1个元素的数列,再依次调用Merge()函数进行归并。

void MergeSort(vector<int> &buf, int low, int high)
{
	if (low < high)
	{
		int mid = (low + high) / 2;	  //从中间划分成两个子序列
		MergeSort(buf, low, mid);	  //对左侧子序列进行递归调用
		MergeSort(buf, mid + 1, high);	  //对右侧子序列进行递归调用
		Merge(buf, low, mid, high);	  //归并
	}
}
  • 多路归并排序

       归并排序多用于外部排序,之前说过了,外部排序的定义是:

       外部排序:指在排序期间元素无法全部同时存放于内存中,必须在排序过程中根据需求不断在内外存之间移动的排序。

       外部排序通常采用归并排序方法,它包括两个相对独立的阶段:首先,根据内存缓冲区大小,将外存上含n个记录的文件分成若干长度为h的子文件,依次读入内存并利用有效的内部排序方法对他们进行排序,并将排序后得到的有序子文件重新写回外存,通常称这些有序子文件为“归并段”或“顺串”,然后,对这些归并段进行逐趟归并,使归并段(有序的子文件)逐渐由小到大,直至得到整个有序文件为止。

       额,这个其实我没怎么学,只知道概念,没用过,就不细谈了,以后用到再学习补充。

 

Three 非比较排序

       非比较排序算法不通过比较两个数的大小来排序。因此可以突破基于比较的排序算法的时间下限。当然,非比较排序有其局限性,所以并不常用。

(断电了,以后再补)

  • 计数排序

       计数排序有几种变化,先从最简单的讲起。

       最初的计数排序就是单纯的记录数据然后排序。其过程是:

  1. 遍历一趟待排序列buf,找出其中元素的最大值max
  2. 建立一个大小等于(max+1)的数组 buf_s ,全体赋值为0
  3. 遍历一趟buf,若 buf[i] = num,则 buf_s [num] 加 1
  4. 最后遍历一趟buf_s,若buf_s[i]>0,则输出i,同时buf_s[i]--

具体代码为:

//计数排序 [1.0]
void CountSort(vector<int>&buf)
{
	int max = buf[0];	                //定义max
	for (int i = 0; i < buf.size(); i++)	//遍历待排序列找到最大值max
	{
		if (buf[i] > max)
		{
			max = buf[i];
		}
	}

	vector<int> buf_s(max + 1, 0);		//创建一个大小为max+1的数组buf_s

	for (int i = 0; i < buf.size(); i++)	//遍历待排序列,录入buf_s数组
	{
		buf_s[buf[i]]++;		
	}

	for (int i = 0, t = 0; i < max + 1; i++)	//遍历buf_s存入buf数组
	{
		for (int j = buf_s[i]; j > 0; j--, t++)
		{
			buf[t] = i;
		}
	}
}

       但是这样存在一些问题,比如,若待排序列为{995,996,997,998,999},则需要一个大小为1000的数组。因此做出如下改进:第一次遍历的同时获得max和min,建立数组为max-min+1,第二趟录入数据时,下标需减去一个min的偏移量。具体代码类似一,不再赘述。

      

      

  • 桶排序

 

  • 基数排序

 

 

 

 

 

 

 

相关文章: