内部排序算法的比较

最近对内部排序进行复习,深入掌握各种排序算法的思想、排序过程和特征,对一些常用排序算法的关键代码进行比较,最终为了在工作中看到特定序列有选择最优排序算法的能力。
排序脑图

一、直接插入排序

这种排序的思想是,在待排序的数组中,设置一段排序区间让他始终保持有序并不断增大,直到覆盖整个数组,就完成了对数组的排序。

这就是最简单的直接插入排序,它的时间复杂度 是 O(N)~O(N^2)

当待排序集合本身接近有序时,它的时间复杂度最小,

当待排序集合本身时逆序集合时,它的时间复杂度最大

具体代码如下:

void DirectInsertionSort(int *arr, int sz)
{
	assert(arr);
	if (sz <= 1)
	{
		return;
	}
	for (int i = 0; i < sz - 1; i++)
	{
		if (arr[i + 1] < arr[i])
		{
		        //end之前的元素都是有序的,tmp表示这个集合接下来的一个元素
			int tmp = arr[i + 1];
			int end = i;
			//将tmp放到排序数组中合适的位置
			while (end >= 0 && arr[i] > tmp)
			{
				arr[end + 1] = arr[end];
				end--;
			}
			arr[end + 1] = tmp;
		}
	}
}

二、希尔排序

简单的说,希尔排序还是插入排序的一种

直接插入算法 比较适合用于接近顺序的数组,不适合于接近完全逆序的数组,

而希尔排序的过程,则是让数组变得越来越有序化,直到完全有序的过程

具体步骤如下:

1、指定一个间隔,gap将数组划分成若干组: (0, gap, gap+gap, …) (1, gap+1, gap+gap+1, …)

分别对每一组的最后一个元素进行单趟插入

2、不断减小gap,重复上述的步骤

3、直到gap为1时,相当于一次普通,但比较高效的的插入排序

根据这样的算法,可以得出结论:

当数组是完全逆序的情况下,希尔排序的优化程度最高

当数组是完全有序的情况下,希尔排序没有优化

时间复杂度为O(N) ~ O(N^2) 但实际上,几乎不会慢到O(N^2)的情况

具体代码如下:

void ShellSort(int *arr, int sz)
{
	assert(arr);
	if (sz <= 1)
	{
		return;
	}
	int gap = sz;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		//将数组分割成若干部分,并分别对每部分进行一趟排序
		for (int i = 0; i < sz - gap; i++)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0 && tmp < arr[end])
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			arr[end + gap] = tmp;
		}
	}
}

三、冒泡排序

冒泡排序,每一趟都遍历一次待排序的数组,当遇到当前元素比下一个元素大的情况,则交换这两个元素

所以,其效率比直接选择排序略高

经过优化后,最快能达到 O(N)

所以其时间复杂度为 O(N) ~ O(logN)

具体代码如下:

void BubbleSort_1(int *arr, int sz)
{
	assert(arr);
	if (sz <= 1)
	{
		return;
	}
	for (int i = 0; i < sz - 1; ++i)
	{
		bool flag = true;
		for (int j = 0; j < sz - i - 1; ++j)
		{
			if (arr[j] > arr[j + 1])
			{
				std::swap(arr[j], arr[j + 1]);
				flag = false;
			}
		}
		if (flag == true)
		{
			return;
		}
	}
}

四、快速排序

快速排序的思想,举个例子说明,待排序的数组如下:

2    5    1    6    4    7    3

进行快速排序,每趟排序之后的数组分别为:

[ 2 1 ] 3 [ 6 4 7 5 ]

1    2    3    [4]    5    [7    6]

1    2    3    4    5    6    7

其中这些红色的元素,左边都是比他小的数,右边都是比它大的数。

以上就是快速排序的思想:

每次从数组中提取一个元素出来,放在合适的位置,满足该位置左边的数都小于它本身,而该位置右边的数都大于它本身。

以上是单趟排序,经过单趟排序,一个元素已经放在了合适的位置,并且以该元素的值为分界,有2个区间,再对以上两个区间进行如上的单趟排序,当两个区间都没有元素的时候,整个数组就已经有序了。

具体代码如下:

//返回中间值的索引
int GetMidIndex(int *arr, int left, int right)
{
	int mid = left + (right - left) / 2;		//2
	if (arr[left] < arr[mid])
	{
		if (arr[mid] < arr[right])
		{
			return mid;
		}
		else if (arr[left] < arr[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else      //arr[left] > arr[mid]
	{
		if(arr[mid] > arr[right])
		{
			return mid;
		}
		else if (arr[left] > arr[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}
//单趟排序
int _PortSort(int *arr, int left, int right)
{
	assert(arr);
	//优化
	int mid = GetMidIndex(arr, left, right);
	std::swap(arr[mid], arr[right]);
	int key = arr[right];
	int begin = left;
	int end = right - 1;
	while (begin < end)
	{
		//begin找比key大的为止
		while (begin < end && arr[begin] < key)
		{
			++begin;
		}
		//end找比key小的为止
		while (begin < end && arr[end] >= key)
		{
			--end;
		}
		//找到了
		if (begin < end)
		{
			std::swap(arr[begin], arr[end]);
		}
	}
	if (arr[begin] > arr[right])
	{
		std::swap(arr[begin], arr[right]);
		return begin;
	}
	else   //这种情况下,说明本趟排序没有找到合适的位置
	{
		return right;
	}
}
void _QuickSort(int *arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int div = _PortSort(arr, left, right);	//单趟排序,得到中间位置
	_QuickSort(arr, left, div - 1);
	_QuickSort(arr, div + 1, right);
}
void QuickSort(int *arr, int sz)
{
	assert(arr);
	if (sz <= 1)
	{
		return;
	}
	_QuickSort(arr, 0, sz - 1);
}

快速排序的优化主要为以下两方面:

1、“三次取中”

2、为了节约压栈开销,当递归若干次,区间已经足够小时,对小区间的处理不要继续递归调用了,而是用其他排序(如插入排序)

快速排序的时间复杂度是:O(logN) ,最慢有可能达到O(N^2),不过采用“三次取中”优化的快速排序,是可以避免这种最坏情况的

五、选择排序

选择排序是很好理解的一种排序算法,但也是最慢的一种排序算法。

它的实现步骤是:

选最大的元素放到队尾,再在剩下的元素中选最大的放之前队尾的前一个位置

所以它的时间复杂度 始终是 O(N^2)
具体代码如下:

void SelectionSort_Normal(int *arr, int sz)
{
	assert(arr);
	if (sz <= 1)
	{
		return;
	}
	while (sz > 0)
	{
		int MaxIndex = 0;
		for (int i = 0; i < sz; i++)
		{
			if (arr[i] > arr[MaxIndex])
			{
				MaxIndex = i;
			}
		}
		if (MaxIndex != (sz - 1))
		{
			std::swap(arr[sz - 1], arr[MaxIndex]);
		}
		sz--;
	}
}

实现速度较慢,做一个优化每次顺便选出最小的元素放到当前的第一个位置,提高一倍的速度,从时间复杂度的角度来看,依然是 O(N^2)
具体代码如下:

/*
选择排序——直接选择排序2(每次选最大的数放到队尾,选最小的数放队首)
时间复杂都为 O(N^2 / 2) (效率翻倍)
*/
void SelectionSort_Improve(int *arr, int sz)
{
	assert(arr);
	if (sz <= 1)
	{
		return;
	}
	int front = 0;
	int end = sz - 1;
	while (front < end)
	{
		int MaxIndex = front;
		int MinIndex = front;
		for (int i = front; i <= end; ++i)
		{
			if (arr[i] > arr[MaxIndex])
			{
				MaxIndex = i;
			}
			if (arr[i] < arr[MinIndex])
			{
				MinIndex = i;
			}
		}
		int MaxVal = arr[MaxIndex];
		int MinVal = arr[MinIndex];
		if (MaxIndex != end)
		{
			std::swap(arr[MaxIndex], arr[end]);
		}
		//修正MinIndex
		if (MinIndex == end)
		{
			MinIndex = MaxIndex;
		}
		if (MinIndex != front)
		{
			int tmp = arr[front];
			arr[front] = MinVal;
			arr[MinIndex] = tmp;
		}
		++front;
		--end;
	}
}

六、堆排序

堆排序也是选择排序,上述的选择排序每次都得在剩下的元素中选择最大的元素并放在最后

而堆排序则借助堆,利用堆的特性,在选择最大元素这一步大大提高了效率

堆排序是一种很高效的算法,堆是一种数据结构,以二叉树的形式表现出来

堆又分为大堆、小堆,其根节点分别是所存放元素中最大、最小的

二叉树可以用指针的形式表示,也可以用数组下标的形式表示,这里采用下标的形式表示

排序的思路为:

(这里采用大堆的形式)

1、将所有元素放入堆中

2、这时堆顶元素就是最大的,将它与堆中最后一个元素交换,此时数组中最后一个元素就是所有元素中最大的那一个,接下来对堆顶的元素执行一次向下调整,将其放在合适的位置

(以上是一趟排序)

3、不断重复第2步,直到所有元素有序 ,

每次选出当前堆中最大的元素放在最后面,因此最终得到的是升序的数组。

堆排序效率很高,并且效率几乎不受数组本身的影响,是一种比较高效的排序算法。

其时间复杂度为 O(log2(N))
具体代码如下:

//向下调整
void AdjustDown(int *arr, int sz, int parent)
{
	int child = parent * 2 + 1;
	while (child < sz)
	{
		if (child + 1 < sz && arr[child + 1] > arr[child])
		{
			++child;
		}
		if (arr[child] > arr[parent])
		{
			std::swap(arr[child], arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int *arr, int sz)
{
	assert(arr);
	if (sz <= 1)
	{
		return;
	}
	//建堆
	for (int i = (sz - 2) / 2; i >= 0; --i)
	{
		AdjustDown(arr, sz, i);
	}
	int end = sz - 1;
	while (end > 0)
	{
		std::swap(arr[0], arr[end]);
		AdjustDown(arr, end, 0);
		--end;
	}
}

七、归并排序

归并排序是用分治思想,分治模式在每一层递归上有三个步骤:

  • 分解(Divide):将n个元素分成个含n/2个元素的子序列。
  • 解决(Conquer):用合并排序法对两个子序列递归的排序。
  • 合并(Combine):合并两个已排序的子序列已得到排序结果。

具体代码如下:

void Merge(int *a, int *tmp, int begin1, int end1, int begin2, int end2)
{
 int index = begin1;
 while (begin1 <= end1 && begin2 <= end2)
 {
  if (a[begin1] < a[begin2])
  {
   tmp[index] = a[begin1];
   ++begin1;
  }
  else
  {
   tmp[index] = a[begin2];
   ++begin2;
  }
  ++index;
 }
 while (begin1 <= end1)
 {
  tmp[index++] = a[begin1++];
 }
 while (begin2 <= end2)
 {
  tmp[index++] = a[begin2++];
 }
}

void _MergeSort(int *a, int *tmp, int left, int right)
{
 if (left >= right)
 {
  return;
 }
 int mid = left + (right - left) / 2;
 _MergeSort(a, tmp, left, mid);
 _MergeSort(a, tmp, mid + 1, right);
 Merge(a, tmp, left, mid, mid + 1, right);
 memcpy(a + left, tmp + left, (right - left + 1) * sizeof(int));
}

void MergeSort(int *a, size_t n)
{
 int *tmp = new int[n];
 _MergeSort(a, tmp, 0, n - 1);
 delete[] tmp;
}

八、基数排序

基数排序非常特别,不是基于比较进行排序,而是采用多关键字排序,也就是对关键字的各位的大小进行排序,分为最高位优先(MSD)和最低位优先(LSD)。
具体代码如下:

#define MAX 20
//#define SHOWPASS
#define BASE 10

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

void radixsort(int *a, int n) {
  int i, b[MAX], m = a[0], exp = 1;

  for (i = 1; i < n; i++) {
    if (a[i] > m) {
      m = a[i];
    }
  }

  while (m / exp > 0) {
    int bucket[BASE] = { 0 };

    for (i = 0; i < n; i++) {
      bucket[(a[i] / exp) % BASE]++;
    }

    for (i = 1; i < BASE; i++) {
      bucket[i] += bucket[i - 1];
    }

    for (i = n - 1; i >= 0; i--) {
      b[--bucket[(a[i] / exp) % BASE]] = a[i];
    }

    for (i = 0; i < n; i++) {
      a[i] = b[i];
    }

    exp *= BASE;

#ifdef SHOWPASS
    printf("\nPASS   : ");
    print(a, n);
#endif
  }
}

int main() {
  int arr[MAX];
  int i, n;

  printf("Enter total elements (n <= %d) : ", MAX);
  scanf("%d", &n);
  n = n < MAX ? n : MAX;

  printf("Enter %d Elements : ", n);
  for (i = 0; i < n; i++) {
    scanf("%d", &arr[i]);
  }

  printf("\nARRAY  : ");
  print(&arr[0], n);

  radixsort(&arr[0], n);

  printf("\nSORTED : ");
  print(&arr[0], n);
  printf("\n");

  return 0;
}

总结

类别排序方法平均情况最好情况最坏情况空间复杂度稳定性
插入排序插入排序0(NA2)〇(N)0(NA2)0⑴稳定
-Shell排序0(NA1.3)〇(N)0(NA2)0⑴不稳定
选择排序选择排序0(NA2)0(NA2)0(NA2)0⑴不稳定
-堆排序0(N*lgN)0(N*lgN)0(N*lgN)0⑴不稳定
交换排序冒泡排序0(NA2)〇(N)0(NA2)0⑴稳定
-快速排序0(N*lgN)0(N*lgN)0(NA2)O(lgN)不稳定
归并排序归并排序0(N*lgN)0(N*lgN)0(N*lgN)〇(N)稳定
  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为您提供一些关于内部排序算法比较课程设计的建议。 首先,您需要确定该课程设计的目标和学习内容。您可以考虑以下几个方面: 1. 内部排序算法的基本概念和分类,如插入排序、选择排序、冒泡排序、归并排序、快速排序等。 2. 掌握算法复杂度分析的方法,如时间复杂度、空间复杂度等。 3. 了解不同排序算法的优缺点,如稳定性、原地排序、递归和非递归等。 4. 实现不同排序算法的代码,并进行性能测试和比较。 5. 分析排序算法的优化方案,如基于算法改进、数据结构设计、并行化等。 接下来,您可以根据以上考虑的内容,制定课程设计的详细计划。以下是一个可能的课程设计计划: 1. 第一周:介绍内部排序算法的基本概念和分类,讲解插入排序、选择排序、冒泡排序等简单排序算法的实现和性能分析。 2. 第二周:学习归并排序和快速排序等高级排序算法的实现和性能分析,分析算法的时间复杂度和空间复杂度。 3. 第三周:实现不同排序算法的代码,并进行性能测试和比较,讨论不同算法的优缺点和适用场景。 4. 第四周:分析排序算法的优化方案,如基于算法改进、数据结构设计、并行化等,实现优化后的算法并测试性能。 5. 第五周:总结和回顾整个课程设计的过程,讨论可能的改进和未来研究方向。 希望这些建议能够对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客范儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值