数据结构算法(3)--排序

数据结构算法(3)--排序

总结并记录常见的几种排序算法.


稳定排序算法

Note:
稳定性是指当数组中有两个相同值的元素p和q ,其排序完成后q依旧在p右边。

(1)插入排序

说明: 插入排序在n较小且原始数组基本有序的情况下效果最佳。

//插入排序,稳定排序
void InsertSort(int a[], int n)
{
	int temp;
	for (int i = 1; i < n; ++i)
	{
		int j = i - 1;
		temp = a[i];
		while (j >= 0 && temp < a[j])//逆序
		{
			a[j + 1] = a[j];//后移
			--j;
		}
		a[j + 1] = temp;
	}
}

(2)冒泡排序

优化:在比较的过程中如果没有发现交换情况则提前终止

void BuddleSort(int a[], int n)
{
	bool NoSwap;
	int i, j;
	for (i = 0; i < n - 1; ++i)
	{
		NoSwap = true;
		for (j = n - 1; j > i; --j)
		{
			if (a[j] < a[j - 1])
			{
				swap(a, j, j - 1);
				NoSwap = false;
			}
		}
		if (NoSwap)
			return;
	}
}

(3)归并排序

说明:
基于分治思想的排序算法,其基本思想为:

  • 将序列划分为两个子序列;
  • 分别对每个子序列进行归并排序;
  • 有序子序列合并
    其算法框架为:
void MergeSort(int a[], int tempa[], int left, int right)
{
	int middle;
	if (left < right)
	{
		middle = (left + right) / 2;
		MergeSort(a, tempa, left, middle);//左子序列
		MergeSort(a, tempa, middle + 1, right);//右子序列
		Merge(a, tempa, left, right, middle);
	}
}

归并算法为:

//从左往右扫描两个有序子序列,将其归并到新数组
void Merge(int a[], int tempa[], int left, int right, int middle)
{
	int i, j, index1, index2;
	for (j = left; j <= right; ++j)//暂存
		tempa[j] = a[j];
	index1 = left;//L子序列起点
	index2 = middle + 1;//R子序列起点
	i = left;//从左开始归并
	while (index1 <= middle && index2 <= right)
	{
		if (tempa[index1] <= tempa[index2])
			a[i++] = tempa[index1++];
		else
			a[i++] = tempa[index2++];
	}
	while (index1 <= middle)//整理余下的L序列
		a[i++] = tempa[index1++];
	while (index2 <= right)//整理余下的R序列
		a[i++] = tempa[index2++];
}

对归并排序的优化

由于上面的Merge函数中存在大量的比较操作从而使得代码效率较低,为了解决这一问题,故采用以下优化方式:

  • 对基本有序的序列直接进行插入排序;
  • 采用R.Sedgewick优化:归并时从两端开始处理,向中间推进。具体做法为:将R子序列进行倒置后与L序列进行归并。
    代码如下:
void Merge(int a[], int tempa[], int left, int right, int middle)
{
	int i, j,k, index1, index2;
	for(i = left;i<=middle;++i)
	    tempa[i] = a[i];
	for (j = 1; j <= right-middle; ++j)//逆序存储R序列
		tempa[right-j+1] = a[j+middle];
	for(index1 = left,index2 = right,k=left;k<=right;++k)
	{
		if(tempa[index1]<=tempa[index2])
			a[k]=tempa[index1++];
		else
			a[k]=tempa[index2--];
	}
	
}

(4)桶排序

其主要特点为:

  • 非比较排序:将具有相同值的记录分配于同一桶内,然后依次按照编号从桶中取出;
  • 需要事先直到序列中的记录都位于某个小区间[0,m)内;
  • 适用于m(元素值范围)较小的情况。
    其中代码实现为:
//桶排序
void BucketSort(int a[], int n, int max)
{
	int *tempa = new int[max], *count = new int[max];
	int i;
	for (i = 0; i < n; ++i)
		tempa[i] = a[i];
	for (i = 0; i < n; ++i)
		count[i] = 0;
	for (i = 0; i < n; ++i)//统计每个值出现次数
		count[a[i]]++;
	for (i = 0; i < max; ++i)//统计前i桶总元素个数
		count[i] = count[i - 1] + count[i];
	for (i = n - 1; i >= 0; --i)
		a[--count[tempa[i]]] = tempa[i];
	delete[] tempa;
	delete[] count;
}

非稳定排序算法

(1)shell排序(以shell(2)为例)

说明:shell为插入排序的变形,其基本思想为:

  • 先将序列转化为若干小序列,在这些小序列内进行插入排序;
  • 逐渐增加小序列的规模,同时减少小序列个数。使之更有序;
  • 最后对整个序列进行排序:扫尾直接进行插入排序。

但shell(2)的选取的增量(2的指数)间不互质,导致重复排序。常见的解决办法是使用shell(3)或Hibbard增量排序。
Hibbard增量:{2k-1,2(k-1)-1,...,7,3,1}

void ModInsSort(int array[], int n, int delta)
{
	int i, j;
	for (i = delta; i < n; i += delta)
		for (j = i; j >= delta; j -= delta)//已delta为步长向前寻找逆置位,对其进行调整
		{
			if (array[i] < array[j - delta])
				swap(array, j, j - delta);
			else
				break;
		}
}

//shell(2)排序
void shellSort(int a[], int n)
{
	int i, delta;
	for (delta = n / 2; delta > 0; delta /= 2)
		for (i = 0; i < delta; ++i)
			ModInsSort(&a[i], n - i, delta);
	ModInsSort(a, n, 1);//如果不能保证最后一个delta的间距为1,可加入
}

(2)直接选择排序

没什么需要说的,哈哈

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

(3)快速排序

说明:
基于分治思想的排序算法,其基本思想为:

  • 选择轴值(pivot):
  • 将序列划分为两个子序列L和R,使得L中所有记录小于等于轴值,R中的所有记录都大于轴值;
  • 对L和R递归进行快速排序(分治)。

轴值选择:尽可能使得L和R长度相等。

int SelectPivot(int left, int right)
{
	return (left + right) / 2;//中间记录作为轴值
}

快速排序算法框架:

void QuickSort(int a[], int left, int right)
{
	if (right <= left)
		return;
	int pivot = SelectPivot(left, right);//选择轴值
	swap(a, pivot, right);//将轴值放在序列末端
	pivot = Partition(a, left, right);//序列整理
	QuickSort(a, left, pivot - 1);//对L进行递归排序
	QuickSort(a, pivot + 1, right);//对R进行递归排序
}

一次序列整理过程:

  • 选择轴值并存储;
  • 将序列最后一个元素放在轴值位置(swap(a,pivot,right));
  • 初始化下标i,j使其分别指向头和尾;
  • i递增直到遇到比轴值大的元素,将此元素放到j位置,之后j递减直到遇到比轴值小的元素,将该元素放到i的位置;
  • 重复上步,直到i==j,最后将轴值放到i的位置。

其代码如下:

int Partition(int a[], int left, int right)
{
	int l = left, r = right;
	int temp = a[right];//存储轴值
	while (l != r)
	{
		while (a[l] <= temp && l < r)//右移
			++l;
		if (l < r)
		{
			a[r] = a[l];
			--r;
		}
		while (a[r] >= temp && l < r)//左移
			--r;
		if (l < r)
		{
			a[l] = a[r];
			++l;
		}
	}
	a[l] = temp;//轴值填到l位置
	return l;//返回pivot位置
}

快排的可能优化为:

  • 将left和right的间距小于阈值(28?)时,改用简单排序算法代替;
  • 用栈来减少递归操作;

(4)堆排序

堆排序是一种不稳定排序,其时间复杂度为O(nlogn)。

posted @ 2019-03-24 11:37  会煮面  阅读(186)  评论(0编辑  收藏  举报