常用排序算法总结

常用的排序算法总结(一)

  • 定义

    排序算法其实就是通过特定的算法将一组数据或多组数据按照既定的模式或者规则进行排列

  • 排序算法稳定性

    排序的稳定性是指在排序的过程中如果有两个数据的主关键词一致,那么结果如果存在多样性,那么就说明该排序方法不稳定,反之说明稳定。
    假设a=b,在排序之前a的位置在b的前面,在排序之后a的位置仍然在b的前面,那么说明稳定,反之就代表不稳定。

  • 内排序和外排序

    内排序是指在排序的过程中,待排序的数据全部放在内存中。外排序是指由于数据比较多,数据需要在内存和硬盘之间进行数据读写才能进行。这次主要总结几个比较重要的内排序算法。
    内排序算法的优劣主要可以通过时间复杂度空间复杂度以及算法的复杂度(算法本身的难度)来衡量。这次主要总结几个常用的排序算法,冒泡排序,插入排序,希尔排序,快速排序以及堆排序。
    常用排序算法总结_第1张图片

  • 排序过程中用到的结构体和交换函数

本篇文章的代码主要通过c/c++语句书写

typedef struct 
{
     
	int *arr;//数组指针,用来传递可变数组
	int length;//指定数组的大小
}sqList;

void swap(sqList*L, int i, int j)//交换函数,用来交换数组中指定的两个数据
{
     
	int temp = L->arr[i];
	L->arr[i] = L->arr[j];
	L->arr[j] = temp;
}

冒泡排序

  • 实现思想
    冒泡排序是一种交换排序他的实现思想是两两数据之间进行比较,如果正序则继续比较,如果反序则进行交换,这样每循环比较一次就把最大或者最小的数据放到最左或者最右边,就像是冒泡,一次比较完把最大的泡泡浮出来,然后继续比较剩下的n-1个数据,这样进行n次循环比较之后就把顺序排好了。

  • 代码实现
    优化过后,如果在一次循环排序的过程中没有数据再做比较,说明后续数据已经有序,不需要排列,这时候可以通过一个标志位来判断,没有比较后直接退出循环,减少时间的复杂度

void BubSort(sqList*L)
{
     
	if (L->length == 0)
	{
     
		cout << "数组无数据" << endl;
		return;
	}
	int flag = 1;//标志位,判断后续的数据中是否还存在比较
	for (int i = 0; i < (L->length - 1)&&flag; ++i)
	{
     
		flag = 0;//默认不再存在比较,置0
		for (int j = L->length - 2; j >= i; j--)
		{
     
			if (L->arr[j] > L->arr[j + 1])
			{
     
				flag = 1;//当存在比较时再置一
				swap(L, j, j + 1);
			}		
		}
	}
}
  • 时间复杂度分析
    时间复杂度最佳情况:T(n) = O(n)
    时间复杂度最差情况:T(n) = O(n2)
    时间复杂度平均情况:T(n) = O(n2)

选择排序

  • 实现思想
    选择排序就是在一次循环过程中把最小或者最大的数据找出来,放到最左或者最右边,然后在从剩下的n-1个数据中找出最大或者最小的数据,然后依次排序,最终排列完成。即从n-i个数据中找出最小的值放在i处,直到 i==(n-1)
  • 代码实现
void ChoiceSort(sqList*L)
{
     
	if (L->length == 0)
	{
     
		cout << "数组无数据" << endl;
		return;
	}
	for (int i = 0; i < L->length; ++i)
	{
     
		int min = i;//用来最后确定是否需要数据交换
		for (int j = i + 1; j < L->length - 1; ++j)
		{
     
			if (L->arr[min] > L->arr[j])
			{
     
				min = j;//如果有更小的值,将其下标赋值给min,用来进行数据交换
			}		
		}
		if (min != i)
		{
     
			swap(L, i, min);
		}
	}
}
  • 时间复杂度分析
    时间复杂度最佳情况:T(n) = O(n)
    时间复杂度最差情况:T(n) = O(n2)
    时间复杂度平均情况:T(n) = O(n2)

插入排序

  • 实现思想
    插入排序的基本原理就是把一个数据插入到一个已经排好序的数据表中,从而得到一个新的排序表。
    原理可以类比扑克牌,先把最左边的牌当做定标,然后从左边第二张牌开始一次往前比较插入,如果该数据的值比前面数据小,那么前面数据后移,直到遇到比自己小的数据(或者到达第一位),然后进行相应的放置插入。
  • 代码实现
void InsertSort(sqList*L)
{
     
	if (L->length == 0)
	{
     
		cout << "数组无数据" << endl;
		return;
	}
	for (int i = 1; i < L->length; ++i)//把下标等于0的数据作为定标,从他的下一个数据依次进行插入
	{
     
		if (L->arr[i] < L->arr[i - 1])//如果当前数据比前一数据小才进行插入,否则有序
		{
     
			int j = 0;
			int temp = L->arr[i];
			//依次和前面的所有数据比较,直到遇到更小的数据
			for ( j= i - 1; j >= 0 && L->arr[j] > temp; --j)
				L->arr[j + 1] = L->arr[j];
			L->arr[j + 1] = temp;//插入数据到合适位置
		}
	}
}
  • 时间复杂度分析
    时间复杂度最佳情况:T(n) =O(n2)
    时间复杂度最差情况:T(n) = O(n2)
    时间复杂度平均情况:T(n) = O(n2)

希尔排序

  • 实现思想
    希尔排序是对插入排序的一个升级,又叫增量缩小排序,是第一个突破时间复杂度O(n2)的排序
    实现的主要思想是讲原本大量的数据进行分组,然后对不同分组的数据序列进行直接插入排列,当数据基本有序的时候再进行一次整体的插入排列。
    所谓基本有序就是小的基本在前面,大的基本在后面,不大不小的基本在中间。而我们实现这一步的思想就是把相隔某个增量的数据组成一个子序列,然后依次减小这个增量的大小,直到增量为1,这样最后依次就是整体序列的排序。
    为了方便理解从网上找了一个图
    常用排序算法总结_第2张图片
  • 代码实现
void ShellSort(sqList*L)
{
     
	if (L->length == 0)
	{
     
		cout << "数组无数据" << endl;
		return;
	}
	int i, j;
	int increment = L->length;
	do
	{
     
		increment = increment / 3 + 1;//设置**增量**
		for (i = increment; i < L->length; ++i)
		{
     
			if (L->arr[i] < L->arr[i - increment])
			{
     
				int temp = L->arr[i];
				//依次对子序列中的数据实行直接插入排序
				for (j = i - increment; j >= 0 && L->arr[j] > temp; j -= increment)
					L->arr[j + increment] = L->arr[j];
				L->arr[j + increment] = temp;//插入到合适位置
			}
		}
	} while (increment > 1);
}
  • 时间复杂度分析
    时间复杂度最佳情况:T(n) = O(nlog2n)
    时间复杂度最坏情况:T(n) = O(nlog2n)
    时间复杂度平均情况:T(n) =O(nlog2n)

堆排序

  • 实现思想
    了解堆排序之前首先要了解堆这种数据结构,堆是一个完全二叉树,把每个结点的值都大于或者等于其左右孩子的结点的值的堆叫做大顶堆,把每个节点都小于等于其左右孩子的节点的值的堆叫做小顶堆。
    如果按照层序的方式给每个结点编号,那么有
    ki>=k(2*i)
    ki>=k(2*i+1)
    堆排序就是利用堆进行排序,这里主要使用大顶堆,把整个序列按照层序的序号构建成大顶堆,这样最大的数据就在第一个,将其与最后一个数据进行交换,然后再把剩下的n-1个数据序列构成一个大顶堆,反复执行就得到一个有序序列。
    常用排序算法总结_第3张图片
  • 代码实现
void HeapSort(sqList*L)
{
     
	int i;
	for (i = L->length / 2 - 1; i >= 0; i--)//根据二叉树性质,i是所有有子节点的节点
	{
     
		HeapAdjust(L,i,L->length-1);//大顶堆调整函数
	}
	for (i = L->length - 1; i > 0; i--)//把二叉树的根节点(最大节点)排到最后一位
	{
     
		swap(L, 0, i);
		HeapAdjust(L, 0, i - 1);
	}
}

void HeapAdjust(sqList*L, int s, int m)//s是需要调整的节点,m是调整范围
{
     
	int j, temp;
	temp = L->arr[s];
	for (j = 2*s + 1; j <= m; j = j*2+1)//遍历子节点
	{
     
		if (j<m&&(L->arr[j] < L->arr[j + 1]))//找出子节点中较大的一个
			++j;
		if (temp > L->arr[j])//把父节点调整为最大
			break;
		L->arr[s] = L->arr[j];//插入数据
		s = j;
	}
	L->arr[s] = temp;
}
  • 时间复杂度分析
    时间复杂度最佳情况:T(n) = O(nlogn)
    时间复杂度最差情况:T(n) = O(nlogn)
    时间复杂度平均情况:T(n) = O(nlogn)

快速排序

  • 实现思想
    快速排序的思想是在循环之前找到一个“枢轴”,根据这个“枢轴”在一次循环把序列分割成两个子序列,一部分序列的数据都比比枢轴小,另外一部分序列的数据都比枢轴要大,这样既把枢轴的位置找了出来,有对剩下的序列实现基本有序。
    在这里主要通过一个partition函数实现,他的作用是选择一个枢轴,并实现序列的分割,这里枢轴的选择就尤为关键,因为每次都会循环一次,所以枢轴如果区在中间,那么效果是最好的,能够最大程度的实现“基本有序”思想,所以一般采用三序取中的方式来选取枢轴的值
    常用排序算法总结_第4张图片
  • 代码实现
void QuickSort(sqList*L)
{
     
	Qsort(L, 0, L->length - 1);
}
void Qsort(sqList*L, int low, int high)
{
     
	int flag;
	if (high - low <= MAX_INSERT)//判断,当数据量较小时用插入排序更好
	{
     
		//递归进行循环排序
		flag = partition(L, low, high);
		Qsort(L, low, flag - 1);
		Qsort(L, flag + 1, high);
	}
	else
		InsertSort(L);
}
int partition(sqList*L, int low, int high)
{
     
	int flag;
	//使用三序选择法找出一个最优枢轴
	int m = low + (high - low) / 2;
	if (L->arr[m] > L->arr[high])
		swap(L, m, high);
	if (L->arr[low] > L->arr[high])
		swap(L, low, high);
	if (L->arr[low] > L->arr[high])
		swap(L, low, high);
	if(L->arr[m] > L->arr[low])
		swap(L, low, m);
	flag = L->arr[low];//记录最优枢轴
	//对数组排序,使枢轴左边全是小于其的元素,右边全是大于其的元素
	while (low < high)
	{
     
		while (low<high&&L->arr[high]>=flag)
			high--;
		L->arr[low] = L->arr[high];
		while (low<high&&L->arr[low]<=flag)
			low++;
		L->arr[high] = L->arr[low];
	}
	L->arr[low] = flag;
	return low;
}
  • 时间复杂度分析
    时间复杂度最佳情况:T(n) = O(nlogn)
    时间复杂度最差情况:T(n) = O(n2)
    时间复杂度平均情况:T(n) = O(nlogn)

本人编程小白一枚,本文章是根据自己看的书以及别人的博客,并且经过测试改写之后进行编写的一篇文章,文中的图片是从百度上找的便于理解,如有侵权可以联系我删除,如有代码有错误,或者有更好的方法欢迎大佬指出,我会对此表示感谢!!!

你可能感兴趣的:(排序算法)