C语言数据结构之内部排序及其时间复杂度

C语言数据结构之内部排序及其时间复杂度

tips:前些天学习了查找的方法,今天来总结一下九大内部排序。


先总览一下九大内部排序:

内部排序 插入排序 直接插入排序
折半插入排序
希尔排序
交换排序 冒泡排序
快速排序
选择排序 简单选择排序
堆排序
归并排序
基数排序

下面来逐个看看内部排序的过程


1、直接插入排序

直接插入排序是在有序序列中插入元素,使其仍然有序。

思路:

  • 在原来序列中寻找插入位置k;
  • 将原来序列中从k开始的元素全部后移一位;
  • 在位置k插入新元素;

具体实现:

//直接插入排序(数组0号下标不存放关键字)
//在有序序列插入值,使其重新有序
void InsertSort(int arr[], int n)
{
	int i, j;
	for (i = 2; i < n; i++)//第一个元素本身有序,所以从编号为2的元素开始插入排序
	{
		arr[0] = arr[i];//做一个标记,使每次插入有序
		for (j = i - 1; arr[0] < arr[j]; j--)//寻找合适位置插入
			arr[j + 1] = arr[j];
		arr[j + 1] = arr[0];//插入元素(此时arr[0]>=arr[j])
	}
}

2、折半插入排序

折半插入排序是在寻找插入位置时,用折半查找进行定位。

折半插入排序思路与直接插入排序思路相同。

具体实现:

//折半插入排序(用折半查找定位)(数组0号下标不存放关键字)
void BInsertSort(int arr[], int n)
{
	int i, j;
	int low, mid, high;
	for (i = 2; i < n; i++)//第一个元素自身有序,因此从第二个元素开始插入
	{
		arr[0] = arr[i];//做一个标记,使每次插入有序
		low = 1;
		high = i - 1;
		while (low <= high)//用折半查找,确定插入位置
		{
			mid = (low + high) / 2;
			if (arr[mid] > arr[0])
				high = mid - 1;//查找前半部分
			else
				low = mid + 1;//查找后半部分
		}//循环结束,插入位置为high+1
		for (j = i - 1; j >= high + 1; j--)//将high+1以后的元素后移
			arr[j + 1] = arr[j];
		arr[high + 1] = arr[0];//插入元素

	}

}

注意:只有折半查找的循环条件是low<=high,其它不带“=”。


3、希尔排序

希尔排序也叫缩小增量排序,希尔排序根据所选步长,将原表分成若干个特殊子表,然后对子表分别进行直接插入排序,待整个表元素基本有序时,再对整体进行一次直接插入排序。
希尔排序步长选取:d1=⌊n/2⌋;d2=⌊d1/2⌋;直到dk=1;

思路:

  • 根据所选步长,划分子表;
  • 对子表分别进行直接插入排序;
  • 待整个表元素基本有序时,再对整体进行一次直接插入排序;

具体实现:

//希尔排序(数组0号下标不存放关键字)
void ShellSort(int arr[], int n)
{
	for (int dk = n / 2; dk >= 1; dk = dk / 2)//缩小增量,直到增量为1
	{
		for (int i = dk + 1; i < n; i++)//对每个子表分别进行插入排序
		{
			if (arr[i] < arr[i - dk])//选取合适位置插入
			{
				arr[0] = arr[i];//0号元素作为标记
				int j;
				for (j = i - dk; j > 0 && arr[0] < arr[j]; j = j - dk)//元素移动
					arr[j + dk] = arr[j];
				arr[j + dk] = arr[0];//插入元素
			}		
		}
	}
}

4、冒泡排序

思路:

  • 从后往前依次两两比较相邻元素的值(冒泡)
  • 符合条件就进行交换;
  • 直到序列比较结束;

具体实现:

//冒泡排序(数组下标从0开始)
void BubbleSort(int arr[], int n)
{
	for (int i = 0; i < n - 1; i++)//冒泡执行的次数(最后一个元素就不需要再冒泡了)
	{
		int flag = 0;
		for (int j = n - 1; j > i; j--)//一趟冒泡过程
		{
			if (arr[j - 1] > arr[j])
			{
				swap(arr[j - 1], arr[j]);
				flag = 1;
			}
		}
		if (flag == 0)//不发生交换时,证明有序
			return;
	}
}

5、快速排序

思路:

  • 在原表中,任取一个元素作为pivot;
  • 通过pivot,将原表分割(Partition)成两个子表,子表左边全部小于pivot,子表右边全部大于pivot;
  • 直到原表整个有序(low<high);

Partition分割思路:

  • 初始化标记low为划分部分第一个元素位置,high为最后一个元素位置,不断移动并交换元素;
  • high向前移动找到第一个比pivot小的元素;
  • low向后移动找到第一个比pivot大的元素;
  • 交换当前两个位置元素;
  • 继续移动标记,直到low>=high位置;

具体实现:

//快速排序
//快速排序的分割函数
int Partition(int arr[], int low, int high)
{
	int pivot = arr[low];//取low下标的元素为关键字
	while (low < high)
	{
		while (low < high&&arr[high] >= pivot) 
			high--;//从后往前找到比关键字小的元素
		arr[low] = arr[high];//小的,放在左侧部分

		while (low < high&&arr[low] <= pivot)
			low++;//从前往后找到比关键字大的元素
		arr[high] = arr[low];//大的,放在右侧部分
	}
	arr[low] = pivot;//将关键字放在中间位置(左边全部比关键字小,右边全部比关键字大)
	return low;//返回中间元素下标
}

//快速排序主体
void QuickSort(int arr[], int low, int high)
{
	if (low < high)
	{
		int pivot = Partition(arr, low, high);
		//依次对两个子表进行划分
		QuickSort(arr, low, pivot - 1);
		QuickSort(arr, pivot + 1, high);
	}
}


6、选择排序

思路:

  • 每趟在待排元素中选择关键字最小的元素放入前面部分;
  • 直到做完n-1趟;

具体实现:

//选择排序
void SelectSort(int arr[], int n)
{
	for (int i = 0; i < n - 1; i++)//第一次选择出最小的,第二次选择出次小的...剩下最后一个元素不用选择
	{
		int min = i;
		for (int j = i + 1; j < n; j++)
		{
			if (arr[j] < arr[min])
				min = j;//选出最小元素下标
		}
		if (min != i)
			swap(arr[i], arr[min]);//交换
	}
}

7、堆排序

思路:

对所有具有双亲结点的编号从大到小依次做如下调整:

  • 若孩子结点全部小于双亲结点,则不进行调整;
  • 若存在孩子结点大于双亲结点,则将最大孩子结点与双亲结点交换,并对孩子结点也做相同调整,直到出现双亲结点值最大;

具体实现:

//堆排序
//建立大根堆(编号从1开始,编号为0的下标不存放关键字)
void BuildMaxHeap(int arr[], int len)
{
	for (int i = len / 2; i > 0; i--)//将父结点调整为父结点值均大于孩子结点值
		AdjustDown(arr, i, len);
}

//堆排序的调整方法
void AdjustDown(int arr[], int k, int len)
{
	arr[0] = arr[k];//用arr[0]存放根结点值
	
	for (int i = 2 * k; i <= len; i = i * 2)//(遍历,找所有子树)将以k结点为根结点的子树全部调整为根值最大
	{
		if (i < len&&arr[i] < arr[i + 1])//比较左右子树的值,选取大的再与根结点比较
			i++;
		if (arr[0] >= arr[i])
			break;//根结点值大于左右子树,不进行调整
		else
		{
			//将左右子树最大的值作为根结点的值,再依次向下调整为大根堆
			arr[k] = arr[i];//大值放到根结点
			k = i;//修改k的值,向下筛选
		}
		arr[k] = arr[0];//将原根结点的值放到arr[i]
	}
}

//堆排序主体
void HeapSort(int arr[], int len)
{
	BuildMaxHeap(arr, len);//建立大根堆
	for (int i = len; i > 1; i--)//每次遍历,就将大根堆最大值放入数组最后,再将剩余的元素调整为大根堆
	{
		swap(arr[i], arr[1]);
		AdjustDown(arr, 1, i - 1);
	}
}

8、归并排序

(二路归并排序)
思路:

  • 将原表划分成两个一组的子表,并进行排序;
  • 归并两个相邻有序子表,使其成为一个新的有序子表;
  • 直到整个表有序(low<high);

具体实现:

//归并排序
//归并排序的合并两个有序线性表方法
//int brr[11];//头文件中声明
void Merge(int arr[], int low, int mid, int high)//mid将arr划分成两个子序列
{
	int i, j, k;
	for (k = low; k <= high; k++)
		brr[k] = arr[k];//将arr中的值全部赋予brr

	for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++)
	{
		if (brr[i] <= brr[j])//比较brr左右两段元素
			arr[k] = brr[i++];//将较小的放入arr中
		else
			arr[k] = brr[j++];
	}
	//下面两个while只有一个会执行,用来将剩余的值放回
	while (i <= mid)
		arr[k++] = brr[i++];
	while(j<=high)
		arr[k++] = brr[j++];
}

//归并排序主体
void MergeSort(int arr[], int low, int high)
{
	if (low < high)
	{
		int mid = (low + high) / 2;//从中间划分两个子树
		MergeSort(arr, low, mid);//对左子树排序
		MergeSort(arr, mid + 1, high);//对右子树排序
		Merge(arr, low, mid, high);//归并两个有序序列
	}
}


9、基数排序

暂且略-_-


实现完上述排序,接下来我们在main()函数中测试一下;

int main()
{
	int arr1[10] = { 10,3,1,5,6,9,8,7,4,2 };//0号下标存放数组长度
	//直接插入排序
	InsertSort(arr1, 10);
	printf("直接插入排序:");
	for (int i = 1; i < 10; i++)
	{ 
		printf("%d ", arr1[i]);
	}
	printf("\n-----------------------------------\n");

	int arr2[10] = { 10,3,1,5,6,9,8,7,4,2 };//0号下标存放数组长度
	//折半插入排序
	BInsertSort(arr2, 10);
	printf("折半插入排序:");
	for (int i = 1; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	printf("\n-----------------------------------\n");

	int arr3[10] = { 10,3,1,5,6,9,8,7,4,2 };//0号下标存放数组长度
	//希尔排序
	ShellSort(arr3, 10);
	printf("希尔排序:");
	for (int i = 1; i < 10; i++)
	{
		printf("%d ", arr3[i]);
	}
	printf("\n-----------------------------------\n");

	int arr4[10] = { 0,3,1,5,6,9,8,7,4,2 };//值从0号下标开始
	//冒泡排序
	BubbleSort(arr4, 10);
	printf("冒泡排序:");
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr4[i]);
	}
	printf("\n-----------------------------------\n");

	int arr5[10] = { 0,3,1,5,6,9,8,7,4,2 };//值从0号下标开始
	//快速排序
	QuickSort(arr5, 0, 9);
	printf("快速排序:");
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr5[i]);
	}
	printf("\n-----------------------------------\n");

	int arr6[10] = { 0,3,1,5,6,9,8,7,4,2 };//值从0号下标开始
	//选择排序
	SelectSort(arr6, 10);
	printf("选择排序:");
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr6[i]);
	}
	printf("\n-----------------------------------\n");

	int arr7[10] = { 10,3,1,5,6,9,8,7,4,2 };//0号下标存放数组长度
	//堆排序
	HeapSort(arr7, 9);
	printf("堆排序:");
	for (int i = 1; i < 10; i++)
	{
		printf("%d ", arr7[i]);
	}
	printf("\n-----------------------------------\n");

	int arr8[10] = { 10,3,1,5,6,9,8,7,4,2 };//0号下标存放数组长度
	//归并排序
	MergeSort(arr8, 1, 9);
	printf("归并排序:");
	for (int i = 1; i < 10; i++)
	{
		printf("%d ", arr8[i]);
	}
	printf("\n-----------------------------------\n");


	return 0;
	
}

测试结果:
在这里插入图片描述


10、内部排序时间复杂度分析

排序算法 最好时间复杂度 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性
直接插入排序 O(n) O( n 2 n^2 ) O( n 2 n^2 ) O(1) 稳定
冒泡排序 O(n) O( n 2 n^2 ) O( n 2 n^2 ) O(1) 稳定
简单选择排序 O( n 2 n^2 ) O( n 2 n^2 ) O( n 2 n^2 ) O(1) 不稳定
希尔排序 O(1) 不稳定
快速排序 O(n l o g 2 n log_2n ) O(n l o g 2 n log_2n ) O( n 2 n^2 ) O( l o g 2 n log_2n ) 不稳定
堆排序 O(n l o g 2 n log_2n ) O(n l o g 2 n log_2n ) O(n l o g 2 n log_2n ) O(1) 不稳定
2路归并排序 O(n l o g 2 n log_2n ) O(n l o g 2 n log_2n ) O(n l o g 2 n log_2n ) O(n) 稳定
基数排序 O(d(n+r)) O(d(n+r)) O(d(n+r)) O( r ) 稳定

tips:努力到无能为力,拼搏到感动自己。

猜你喜欢

转载自blog.csdn.net/wrlovesmile/article/details/108296590