数据结构与算法-排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cj5785/article/details/89036597

排序是计算机内经常进行的一种操作,其目的是将一组无序的数据元素调整为有序的数据元素

排序的概念

排序数学定义:假设含n个数据元素的序列为{ R1, R2, ···, Rn}
其相应的关键字序列为{ K1, K2, ···, Kn}这些关键字相互之间可以进行比较,即在它们之间存在着这样一个关系 :Kp1 ≤ Kp2 ≤ ··· ≤ Kpn
按此固有关系将上式记录序列重新排列为{ Rp1, Rp2, ···,Rpn}的操作称作排序

排序的稳定性

如果在序列中有两个数据元素r[i]和r[j],它们的关键字k[i] == k [j],且在排序之前,对象
r[i]排在r[j]前面。如果在排序之后,对象r[i]仍在r[j]前面,则称这个排序方法是稳定的;
否则称这个排序方法是不稳定的

多关键字排序

排序时需要比较的关键字多余一个
排序结果首先按关键字1进行排序
当关键字1相同时按关键字2进行排序
当关键字n-1相同时按关键字n进行排序
对于多关键字排序,只需要在比较操作时同时考虑多个关键字即可

排序中的关键操作

  • 比较
    任意两个数据元素通过比较操作确定先后次序
  • 交换
    数据元素之间需要交换才能得到预期结果

内排序和外排序

  • 内排序
    整个排序过程不需要访问外存便能完成
  • 外排序
    待排序的数据元素数量很大,整个序列的排序过程不可能在内存中完成

排序的审判

  • 时间性能
    关键性能差异体现在比较和交换的数量
  • 辅助存储空间
    为完成排序操作需要的额外的存储空间
    必要时可以“空间换时间”
  • 算法的实现复杂性
    过于复杂的排序法会影响代码的可读性和可维护性,也可能影响排序的性能

排序总结

  • 排序是数据元素从无序到有序的过程
  • 排序具有稳定性,是选择排序算法的因素之一
  • 比较和交换是排序的基本操作
  • 多关键字排序与单关键字排序无本质区别
  • 排序的时间性能是区分排序算法好坏的主要因素

选择排序

基本思想

每一趟 (例如第 i 趟,i = 0, 1, …,n-2)在后面 n-i个待排的数据元素中选出关键字
最小的元素, 作为有序元素序列的第 i 个元素。

排序过程

  • 首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
  • 再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
  • 重复上述操作,共进行n-1趟排序后,排序结束

示意图

选择排序
选择排序

示例代码

void SelectionSort(int array[], int len) // O(n*n)
{
	int i = 0;
	int j = 0;
	int k = -1;

	for (i = 0; i < len; i++)
	{
		k = i; //寻找最小元素下标
		for (j = i + 1; j < len; j++)
		{
			if (array[j] < array[k])
			{
				k = j;
			}
		}
		if (k != i)
		{
			int tmp = array[i];
			array[i] = array[k];
			array[k] = tmp;
		}
	}
}

插入排序

基本思想:

元素只有一个元素

排序过程

整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序

示意图

插入排序
插入排序.

示例代码

void InertionSort(int array[], int len) // O(n*n)
{
	int i = 0;
	int j = 0;
	int k = -1;
	int temp = -1;

	for (i = 1; i < len; i++)
	{
		k = i; //待插入位置
		temp = array[k];
		for (j = i - 1; (j >= 0) && (array[j] > temp); j--)
		{
			array[j + 1] = array[j]; //元素后移
			k = j; //k需要插入的位置
		}
		array[k] = temp;//元素插入
	}
}

实质

对线性表执行n-1次插入操作,只是先要找到插入位置

V[0], V[1], ···, V[i-1]已经排好序。这时已经排好序。这时,用V[i]的关键字与V[i-1], V[i-2], ···的关键字进行比较, 找到插入位置即将V[i]]插入, 原来位置上的对象向后顺移

插入排序关键点:

  1. 拿出一个元素,留出位置
  2. 符合条件的元素后移

冒泡排序

排序过程

  • 将第一个记录的关键字与第二个记录的关键字进行比较,若为逆序r[1].key > r[2].key,则交换;然后比较第二个记录与第三个记录;以此类推,直至n-1个记录和第n个记录比较为止–第一冒泡排序,结果关键字最大的记录被安置在最后一个记录上
  • 对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录位置
  • 重复上述步骤,直到在一趟排序中没有进行过交换记录的操作为止

示例代码

void BubbleSort(int array[], int len) // O(n*n)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < len; i++)
	{
		for (j = len - 1; j > i; j--)
		{
			if (array[j] < array[j - 1])
			{
				int temp = array[j];
				array[j] = array[j - 1];
				array[j - 1] = temp;
			}
		}
	}
}

排序改进

在冒泡排序的过程中,可以记录下需要交换的位置,对比交换位置的值,得到一个最值,最后再一次性做交换

希尔排序

希尔排序可以看作插入算法的普遍情况

排序过程

先取一个正整数d1<n,把所有相隔d1的记录放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止
O(n-1.3)
Q(nlogn)
希尔排序是不稳定的

示意图

希尔排序

示例代码

void ShellSort(int array[], int len)
{
	int i = 0;
	int j = 0;
	int k = -1;
	int temp = -1;
	int gap = len;
	do
	{
		gap = gap / 3 + 1; //通常取3 O(n 1.3)
		for (i = gap; i < len; i += gap)
		{
			k = i;
			temp = array[k];
			for (j = i - gap; (j >= 0) && (array[j] > temp); j -= gap)
			{
				array[j + gap] = array[j];
				k = j;
			}
			array[k] = temp;
		}
	} while (gap > 1);
}

快速排序

思想

快速排序是对冒泡排序的一种改进。它的基本思想是:
通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,基准数据排在这两个子序列的中间;
然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

步骤描述

对r[s···]中记录进行一趟快速排序,附设两个指针i和j,设枢轴记录rp=r[s],x=rp.key
初始时令i=s,j=t

  • 首先从j所指的位置向前走索第一个关键字小于x的记录,并和rp交换
  • 再从i所指位置起向后搜索,找到第一个关键字大于x的记录,和rp交换
  • 重复上述步骤,直至i==j
  • 再分别对两个子序列进行快速排序,直到每个子序列只含有一个记录为止

示意图

快速排序

示例代码

//划分过程 第一个元素当枢轴,分成2个有效子序列
int partition(int array[], int low, int high)
{
	int pv = array[low], temp = 0;

	while (low < high)
	{
		while ((low < high) && (array[high] >= pv))
		{
			high--; //比基准大,本来就在右边,所以high前移动
		}

		temp = array[low];
		array[low] = array[high];
		array[high] = temp;

		while ((low < high) && (array[low] <= pv))
		{
			low++;
		}

		temp = array[low];
		array[low] = array[high];
		array[high] = temp;
	}
	//返回枢轴的位置。。。重要
	return low;
}

//让n个元素 依此减少 减少到1个元素的时候,因为1个元素可以看成一个有序的序列
void QSort(int array[], int low, int high)
{
	if (low < high)
	{
		int pivot = partition(array, low, high);
		//对子序列1排序
		QSort(array, low, pivot - 1);
		//对子序列2排序
		QSort(array, pivot + 1, high);
	}
}

void QuickSort(int array[], int len) // O(n*logn)
{
	QSort(array, 0, len - 1);
}

归并排序

排序思想

一个元素,可以看做有序的,是稳定的算法

归并思想

将两个或两个以上的有序序列合并成一个新的有序序列
有序序列v[1]···v[m]和v[m+1]···v[n] -> v[1]···v[n]
只有两路的归并称之为2路归并,还有3路,多路归并
归并排序

归并步骤

检测两个有序序列A和B,C为归并后的新的有序序列
归并排序

对一个数组分成两路,mid中间

  1. 设置i,j和p三个指针,其初始值分别指向这三个记录区的起始位置
  2. 合并时依次比较R[i]和R[j]的关键字,取关键字较小的记录复制到R1[p]中
  3. 然后将复制记录的指针i或j加1,以及指向复制位置的指针p加1
  4. 重复这一过程,直至两个输入的子文件中有一个已全部复制完毕,此时将另一个文件中剩余的记录依次复制到R1中即可

设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low…m],R[m+1…high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low…high]中
二路归并

示例代码

void Merge(int src[], int des[], int low, int mid, int high)
{
	int i = low;
	int j = mid + 1;
	int k = low;

	while ((i <= mid) && (j <= high)) //将小的放到目的地中
	{
		if (src[i] < src[j])
		{
			des[k++] = src[i++];
		}
		else
		{
			des[k++] = src[j++];
		}
	}

	while (i <= mid)  //若还剩几个尾部元素
	{
		des[k++] = src[i++];
	}

	while (j <= high) //若还剩几个尾部元素
	{
		des[k++] = src[j++];
	}
}

//每次分为两路 当只剩下一个元素时,就不需要在划分
void MSort(int src[], int des[], int low, int high, int max)
{
	if (low == high) //只有一个元素,不需要归并,结果赋给des[low]
	{
		des[low] = src[low];
	}
	else //如果多个元素,进行两路划分
	{
		int mid = (low + high) / 2;
		int* space = (int*)malloc(sizeof(int) * max);

		//递归进行两路,两路的划分 
		//当剩下一个元素的时,递归划分结束,然后开始merge归并操作
		if (space != NULL)
		{
			MSort(src, space, low, mid, max);
			MSort(src, space, mid + 1, high, max);
			Merge(space, des, low, mid, high); //调用归并函数进行归并
		}

		free(space);
	}
}

void MergeSort(int array[], int len) // O(n*logn)
{
	MSort(array, array, 0, len - 1, len);
}

排序总结

排序总结

猜你喜欢

转载自blog.csdn.net/cj5785/article/details/89036597