[数据结构]---排序总结(简单代码实现 简单图解 总结)

本文详细介绍了8种排序算法,包括插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序以及计数排序。每种算法都阐述了其基本思想、时间复杂度、空间复杂度和稳定性,并提供了简单的代码实现和图解说明。特别地,计数排序是一种非比较型整数排序算法,适用于特定场景。

目录

排序算法常见的有(7+1)种:

下面先介绍前七种:

算法一:插入排序(插入排序是一种简单直观的排序方法.)

算法二:希尔排序(缩小增量排序)

算法三:选择排序

算法四:堆排序

算法五:冒泡排序 

算法六:快速排序 

算法七:归并排序

还要介绍一种特殊的排序方法-->计数排序

算法八:计数排序

排序算法复杂度及稳定性分析:


排序算法常见的有(7+1)种:

下面先介绍前七种:

 

算法一:插入排序(插入排序是一种简单直观的排序方法.)

算法思想:每次将一个需要排序的数据插入到已经排好序的有序数据序列中,从而得到一个新的、个数加一的有序数据序列.

插入排序的基本操作:

  1. 将待排序序列中第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)。

代码实现:

void InsertSort(int* a, int n)
{
	assert(a);
	for (int i = 1; i < n; ++i)
	{
		//单个元素排序  第一个元素不需要排序
		//找到已经排好序的最后一个元素的位置
		int end = i-1;
		//把end+1位置元素插入到合适的位置
		int tmp = a[i];
		while (end>=0 && a[end]>tmp)
		{
			a[end + 1] = a[end];
			--end;
		}
		//找到合适的位置放
		a[end + 1] = tmp;
	}
}
int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9,122, 4, 7 };
	PrintArray(a, 10);
	InsertSort(a, 10);
	PrintArray(a, 10);
	return 0;
}

简单过程图解说明:

小结:

  1. 时间复杂度 :O(N^2)
  2. 空间复杂度 :O(1)
  3. 稳定性        :稳定
  4. 适合场景     :接近有序序列,时间复杂度趋近于O(N)--> 对于有序序列,时间复杂度O(N) 

 

算法二:希尔排序(缩小增量排序)

希尔排序是插入排序的一种又称“缩小增量排序”,是对直接插入排序算法的一种更高效的改进优化版本。

算法思想(过程):先取一个小于n的整数gap1作为第一个增量,把文件的全部记录分组。所有距离为gap的1倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量gap2<gap1重复上述的分组和排序,直至所取的增量gap =1,即所有记录放在同一组中进行直接插入排序为止。

代码实现:

void ShellSort(int* a, int n)
{
	//gap>1 预排序
	//gap=1 最终排序过程
	int gap=n;
	while (gap>1)
	{
		gap = gap / 3 + 1;  //+1是保证最后一次gap为1插入排序
		for (int i = gap; i<n; i++)
		{
			int end = i-gap;
			int tmp = a[i];
			while (end >= 0 && a[end]>tmp)
			{
				a[end + gap] = a[end];
				end -= gap;
			}
			a[end + gap] = tmp;
		}
	}
}
int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	ShellSort(a, 10);
	PrintArray(a, 10);
	return 0;
}

简单过程图解说明:

小结:

  1. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序
  2. 时间复杂度 :平均:O(1.3)--O(N^2)
  3. 空间复杂度 :O(1)
  4. 稳定性:    不稳定

 

算法三:选择排序

基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的
数据元素排完 。

基本操作:

  1. 第一次从待排序的数据序列中选出最小(或最大)的一个数据,存放在序列的起始位置,
  2. 然后再从剩余的未排序数据序列中寻找到最小(大)数据,然后放到已排序的序列的末尾。
  3. 以此类推,直到全部待排序的数据的个数为零

代码实现(包含普通写法和优化写法):

//基本写法
//void SelectSort1(int* a, int n)
//{
//	int max = 0;
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < n;i++)
//	{
//		max = a[0];
//		for (j = 1; j < n - 1;j++)
//		{
//			if (a[j]>a[max])
//			{
//				max = j;
//			}
//		}
//		if (max!=j-1)
//		{
//			Swap(&a[j - 1], &a[max]);
//		}
//	}
//
//}

//优化写法
void SelectSort2(int* a, int n)
{
	
	int begain = 0;
	int end = n - 1;
	while (begain<end)
	{
		//每一次选一个最大的和最小的,放在相应的位置
		int i;
		int max;
		int min;
		max = min = begain;
		for (i = begain; i <= end;i++)
		{
			if (a[i]<a[min])
				min = i;
			if (a[i]>=a[max])
				max = i;
		}
		//min -->begain  max--->end
		Swap(&a[begain], &a[min]);
		if (max==begain)
		{
			max = min;
		}
		Swap(&a[end], &a[max]);
		begain++;
		--end;
	}
}
/*如果不进行if (max==begain) max = min;操作
  可以使用测试用例1 10 8 9 3 2 4 5 7 6进行操作验证
*/
int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	SelectSort1(a, 10);
	SelectSort2(a, 10);
	PrintArray(a, 10);
	return 0;

}

简单过程图解说明: 

小结:

  1. 时间复杂度 O(N^2)
  2. 空间复杂度 O(1)
  3. 稳定性:  可以变成稳定的

 

算法四:堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。(我们先了解下)

基本思想(操作):

  1.       将初始待排序关键字序列(R1,R2....Rn)构建成大(或小)顶堆(使用向下调整算法),此堆为初始的无序区;
  2.       将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]; 
  3.       由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整(使用向下调整算法)为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

代码实现:

void ShiftDown(int* a, int n, int root)
{
	assert(a);
	int parent = root;
	int child = 2 * parent + 1;//定义左孩子
	//当前节点是否有孩子
	while (child <n)
	{
		//是否有右孩子 如果左孩子存在,选出孩子中最大的那个
		if (child + 1 < n && a[child + 1] > a[child])//左孩子存在且左孩子大于右孩子
			++child;//更新孩子索引
		if (a[child]>a[parent])//判断孩子中最大的节点和父亲节点的关系  如果大于
		{
			//进行交换
			Swap(&a[child], &a[parent]);//传址调用
			//更新下一次调整位置
			parent = child;
			child = 2 * parent + 1;
		}
		//如果小于 结束此次调整
		else
		{
			break;//以parent为根的子树已经是一个大堆了
		}
	}
}

void HeapSort(int*a, int n)
{
	int i;
	//建堆  从最后一颗子树开始: (n - 2)/2   i:表示当前子树的根
	for (i = (n - 2) / 2; i >= 0; --i)
	{
		ShiftDown(a, n, i);
	}
	//排序: 1.swap    2. shiftdown
	int end = n - 1;
	while (end>=1)
	{
		Swap(&a[0], &a[end]);
		ShiftDown(a, end, 0);
		--end;
	}	
}


int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	HeapSort(a, 10);
	PrintArray(a, 10);
	return 0;
}

简单过程图解说明: 

小结:

  1. 时间复杂度: O(N*logN)
  2. 空间复杂度: O(1)
  3. 稳定性:     不稳定

 

算法五:冒泡排序 

基本思想(操作):

1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

3)针对所有的元素重复以上的步骤,除了最后一个。

4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现(包括基本版和优化版):

void BubbleSort1(int* a, int n)
{
	for (int i = n; i > 0;i--)
	{
		for (int j = 1; j < i; j++)
		{
			if (a[j-1]>a[j])
			{
				Swap(&a[j], &a[j - 1]);
			}
		}
	}
}

//优化版
void BubbleSort(int*a, int n)
{
	for (int i = n-1; i > 0;i--)
	{
		int flag = 0;
		for (int j = 0; j < i;j++)
		{
			if (a[j]>a[j+1])
			{
				flag = 1;//标记一次冒泡过程是否发生元素交换
				Swap(&a[j], &a[j + 1]);
			}
		}
		if (flag==0)
		{
			break;
		}
	}
}
int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 12, 4, 7 };
	PrintArray(a, 10);
	//BubbleSort1(a, 10);
	BubbleSort(a, 10);
	PrintArray(a, 10);
	return 0;
}

简单过程图解说明: 

小结:

  1. 时间复杂度 :最坏:O(N^2) 最好:(有序)O(N)
  2. 空间复杂度 :O(1)
  3. 稳定性:    稳定

 

算法六:快速排序 

基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

划分区间方法:将区间按照基准值划分为左右两半部分的常见方式有:

  •  hoare版本
  • 挖坑法
  • 前后指针版本

在这里我们进行优化快排算法还可以用到一种算法(三数取中法)

三数取中法实现:

//三数取中法:保证每一次划分均衡
int getMid(int* a, int left, int right)
{
	int mid = left + (right - left) / 2;
	if (a[mid] > a[left])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else
		{
			//mid>left, right
			if (a[left] > a[right])
			{
				return left;
			}
			else
				return right;
		}
	}
	else
	{
		//mid <= left
		if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			//left >= right, mid
			if (a[mid] > a[right])
			{
				return mid;
			}
			else
				return right;
		}
	}
}

 

代码实现:

基本方法:

//快排基本方法
//快排的一次排序:确定基准值的位置  
int PartSort(int* a, int left, int right)
{
	int key = a[left];
	int start = left;
	while (left < right)
	{
		//先从右边找小于key的值
		while (left < right && a[right] >= key)
		{
			--right;
		}
		//从左边找大于key的值
		while (left < right && a[left] <= key)
		{
			++left;
		}
		Swap(&a[left], &a[right]);
	}
	//key位置确定: left ,right 相遇位置
	Swap(&a[start], &a[left]);
	return left;
}

//快排  
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid = PartSort(a, left, right);
	QuickSort(a, left, mid - 1);
	QuickSort(a, mid + 1, right);
}
int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	QuickSort(a, 0, 9);
	PrintArray(a, 10);
	return 0;
}

简单图解:

优化1:

//快排的一次排序:确定基准值的位置
int PartSort1(int* a, int left, int right)
{
	//三数取中
	int mid = getMid(a, left, right);
	Swap(&a[mid], &a[left]);
	int key = a[left];
	int start = left;
	while (left < right)
	{
		//先从右边找小于key的值
		while (left<right && a[right] >= key)
		{
			--right;
		}
		//从左边找大于key的值
		while (left<right && a[left] <=  key)
		{
			++left;
		}
		Swap(&a[left], &a[right]);
	}
	//key位置确定: left ,right 相遇位置
	Swap(&a[start], &a[left]);
	return left;
}

//快排
void QuickSort1(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int mid = PartSort1(a, left, right);
	QuickSort1(a, left, mid - 1);
	QuickSort1(a, mid + 1, right);
/*
	还可以进行小区间优化
	if (left >= right)
	{
	return;
	}
	//小区间优化: 小区间不调用递归
	else(right - left + 1 < 5)
	{
		InsertSort(a+left,right-left+1)
	}
	else
	{
		int mid = PartSort(a, left, right);
		QuickSort(a, left, mid - 1);
		QuickSort(a, mid + 1, right);
	}
*/
}

int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	QuickSort1(a, 0, 9);
	PrintArray(a, 10);
	return 0;
}

挖坑法:

//挖坑法:  快排的一次排序-->确定基准值的位置
int PartSort2(int* a, int left, int right)
{
	int mid = getMid(a, left, right);
	Swap(&a[mid], &a[left]);

	int key = a[left];
	while (left<right)
	{
		//从右边找小的
		while (left < right && a[right] >= key)
			--right;
		//填坑
		a[left] = a[right];
		//从左边找大的
		while (left < right && a[left] <= key)
			++left;
		a[right] = a[left];
	}
	//左右相遇 存放key 基准值
	a[left] = key;
	return left;
}
//快排  
void QuickSort2(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid = PartSort2(a, left, right);
	QuickSort2(a, left, mid - 1);
	QuickSort2(a, mid + 1, right);
}
int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	QuickSort2(a, 0, 9);
	PrintArray(a, 10);
	return 0;
}

前后指针法:

//快排3前后指针法  prev:最后一个小于key的位置   cur:下一个小于key的位置
int PartSort3(int* a, int left, int right)
{
	int mid = getMid(a, left, right);
	Swap(&a[mid], &a[left]);

	int prev = left;
	int cur = left + 1;
	int key = a[left];
	while (cur <= right)
	{
		if (a[cur] <key && (++prev)!=cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[left]);
	return prev;
}

void QuickSort3(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid = PartSort3(a, left, right);
	QuickSort3(a, left, mid-1);
	QuickSort3(a, mid + 1, right);
}
int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	QuickSort3(a, 0, 9);
	PrintArray(a, 10);
	return 0;
}

非递归版(利用栈)

void QuickSortNonR(int* a, int left, int right)
{
    Stack st;
    StackInit(&st);
    StackPush(&st, left);
    StackPush(&st, right);
    while (StackEmpty(&st) != 0)
    {
        int end = StackTop(&st);
        StackPop(&st);
        int begin = StackTop(&st);
        StackPop(&st);
        int div = PartSort1(a, begin, end);
        if (begin < div - 1)
        {
            StackPush(&st, begin);
            StackPush(&st, div - 1);
        }
        if (div + 1 < end)
        {
            StackPush(&st, div + 1);
            StackPush(&st, end);
        }
    }
}

小结:

  1. 时间复杂度:最好的时间复杂度:O(N*logN)  最坏的时间复杂度(原有序-->变逆序):O(N^2)
  2. 空间复杂度:空间可以复用,最大的递归调用链--->logN
  3. 稳定性 : 不稳定

 

 

算法七:归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法采用分治的思想.

基本步骤:

  1. 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针超出序列尾.  将另一序列剩下的所有元素直接复制到合并序列尾

代码实现:

//分解-->合并
void _MergeSort(int* a, int left, int right, int* tmp)
{
	//区间只剩一个元素,不需要分解和归并
	if (left >= right)
	{
		return;
	}
	//分解
	int mid = left + (right - left) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	//归并
	int begain1 = left; 
	int end1 = mid;
	int begain2 = mid + 1;
	int end2 = right;
	int tmpindex = begain1;
	while (begain1 <= end1 && begain2 <= end2)
	{
		if (a[begain1] <= a[begain2])
		{
			tmp[tmpindex++] = a[begain1++];
		}
		else
		{
			tmp[tmpindex++] = a[begain2++];
		}
	}
	while (begain1 <= end1)
	{
		tmp[tmpindex++] = a[begain1++];
	}
	while (begain2 <= end2)
	{
		tmp[tmpindex++] = a[begain2++];
	}

	//拷贝到原有数组对应区间
	memcpy(a + left, tmp + left, (right - left + 1)*sizeof(int));
}

void MergeSort(int* a, int n)
{
	int* ret = (int*)malloc(n*sizeof(int));
	_MergeSort(a, 0,  n-1 , ret);
	free(ret);
}

int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	MergeSort(a, 10);
	PrintArray(a, 10);
	return 0;
}

简单图解:

小结:

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度: O(N+logN)--O(N)
  3. 稳定性: 稳定

 

还要介绍一种特殊的排序方法-->计数排序

算法八:计数排序

计数排序一种非比较型整数排序算法,不同于前七种,它采用了类似于哈希表的思想方法

基本思想::计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

基本操作:

  1. 根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
  2. 遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
  3. 对额外空间内数据进行计算,得出每一个元素的正确位置;
  4. 将待排序集合每一个元素移动到计算得出的正确位置上。

代码实现:

void CoutSort(int* a, int n)
{
	int i = 0;
	int min = a[0];
	int max = a[0];
	//获取数据的范围   范围: 最大值<--->最小值
	for (i = 1; i < n;++i)
	{
		if (a[i]<min)
		{
			min = a[i];
		}
		if (a[i]>max)
		{
			max = a[i];
		}
	}
	int range = max - min + 1;
	int* CoutArr = (int*)malloc(sizeof(int)*range);
	memset(CoutArr, 0, sizeof(int)*range);
	//计数
	for (int i = 0; i < n; ++i)
	{
		CoutArr[a[i] - min]++;
	}
	//排序
	int index = 0;
	for (i = 0; i < range; ++i)
	{
		while (CoutArr[i]--)
		{
			a[index++] = i + min;
		}
	}
}

int main()
{
	int a[] = { 2, 5, 1, 3, 6, 8, 9, 122, 4, 7 };
	PrintArray(a, 10);
	CoutSort(a, 10);
	PrintArray(a, 10);
	return 0;
}

简单图解:

 

小结:

  1. 时间复杂度:  O(max(N,范围))   
  2. 空间复杂度:  O(范围)
  3. 稳定性:      稳定

补充几种排序算法中用到的函数实现:(打印函数,交换两个变量值函数)

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n;++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int*a, int*b)
{
	int tmp=*a;
	*a = *b;
	*b = tmp;
}

 

排序算法复杂度及稳定性分析:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值