Classic insertion sort, Hill, select, heap row, bubbling, fast row, merge

Sort of introduction

When the data processing, often require a lookup operation, in order to check quickly find accurate, generally require data to be processed by keywords ordered size to find a method using higher efficiency.

The basic concept of the sort

1. Sort
Sort a regular operation in a computer, whose purpose is to set a "random" sequence of records adjusted to "orderly" sequence of records, that is popular to chaotic data element, through a certain Sort method is called key process order.
2. Internal and external sorting sorting
the different memory occupancy data according to a ranking, sorting can be divided into two categories.
Internal sort: the entire ordering process entirely in memory;
external sorting: Due to the amount of recorded data to be sorted is too large, the memory can not accommodate all the data, sort it needs an external memory device to complete.
3. Sort the stability
stability: the file is assumed to be sorted, there are two or more records have the same key, in
the Sort by some method, if these elements relative to the same key the order remains the same, the method of this sort
is stable. Conversely, if the relative order of these elements is the same key is changed, the sort method used is unstable.
Examples : a group of students records sorted by student number, and now need to be sorted according to final grade, when the results are the same, requiring small student number routed to the front. Obviously in this case, you must use a stable sorting method.
Description: bubble, insert, base, belong to merge stable sort, select, quickly, Hill, belonging to the sort of instability.

Classification and complexity of the sort of analysis

Here Insert Picture Description
Time and space complexity and stability
"to explain the complexity of time and space."

Detailed usual sort of

Insertion Sort

1.直接插入排序
基本思想:
在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
优点:稳定,快。
缺点:比较次数不一定,比较次数越多,插入点后的数据移动越多,特别是当数据总量庞大的时候,但用链表可以解决这个问题,虽然也会有额外的内存开销。
过程:
Here Insert Picture Description
代码实现:

// 直接插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int next = a[end + 1];//保存下一个数的值,避免被下一次覆盖
		while (end >= 0 && a[end]>next)//对前面排好序的一部分从后往前再比较,有大的就往后覆盖,下一个还大就覆盖,直到没有大于要判断插入的这个next,跳出循环,
		{
			a[end+1] = a[end];
			end--;
		}
		a[end + 1] = next; //再在终止的位置把要插入的next赋值进去
	}
}

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定
  5. 适用性:插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,那么插入排序还是一个不错的选择。

2.希尔排序
基本思想:
在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。
然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。
过程:
Here Insert Picture Description
代码实现

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	//不能写成大于0,因为gap的值始终>=1,
	while (gap > 1)
	{
		gap = gap / 3 + 1;   //设置增量  //应当gap最后变成1了,才是排完序了 所以这里要加1
		for (int i = 0; i < n - gap; i++)
		{
			    // 这里只是把插入排序的1换成gap即可
				//但是这里不是排序完一个分组,再去
				//排序另一个分组,而是整体只过一遍
				//这样每次对于每组数据只排一部分
				//整个循环结束之后,所有组的数据排序完成
			int end = i;
			int next = a[end + gap];
			while (end>=0 && a[end] > next)
			{
				a[end + gap] = a[end];
				end -= gap;
			}
			a[end + gap] = next;
		}
	}
}

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3—N2)
  4. 稳定性:不稳定

选择排序

1.简单选择排序
基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
过程:
Here Insert Picture Description

代码实现:

 选择排序
void SelectSort(int* a, int n)
{
	for (int i = 0; i < n; i++)             
	{
		int min = i;
		for (int j = i+1; j < n; j++)
		{
			if (a[j] < a[min])//直到最左边找到最小值的下标
			{
				min = j;
			}
		}
		swap(&a[i], &a[min]);//小的就交换到前面去,接着下一轮的比较选择
	}

}

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

2.堆排序
基本思想:
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
过程:
排升序,建大堆,再选择判断
Here Insert Picture Description
代码实现

// 堆排序
void AdjustDwon(int* a, int n, int root)//向下调整算法 大堆
{
	int parent = root;
	int child = parent * 2 + 1;
	while (child < n)
	{
		if ((child + 1) < n && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[child]>a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* a, int n)
{
	// 建堆,先从最后两个叶子上的根(索引为(n - 2) / 2开始建堆
	// 先建最小的堆,直到a[0](最大的堆)
	// 这就相当于在已经建好的堆上面,新加入一个
	// 根元素,然后向下调整,让整个完全二叉树
	// 重新满足堆的性质
	for (int i = (n - 2) / 2; i >= 0; i--)//建大堆
	{
		AdjustDwon(a, n, i);
	}
	int end = n - 1;//最后面的数与堆顶的大数交换,在调整,次大的又调整到堆顶,在交换,在调整
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		end--;
	}
}

堆选择排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

交换排序

1.冒泡排序
基本思想:两个数比较大小,较大的数下沉,较小的数冒起来
过程:
Here Insert Picture Description
实现代码:

// 冒泡排序
void BubbleSort(int* a, int n)
{
	int end = n - 1;
	while (end > 0)// 控制趟数
	{
		int flag = 0;//可能要排序的数组本趟就已经达到有序的,后面的趟数就可以没必要跑了,所以设置一个目标值来记录这这种情况
		for (int i = 1; i <= end; i++)//一一对比,直到这一趟结束
		{
			if (a[i - 1] > a[i])
			{
				swap(&a[i - 1], &a[i]);
				flag = 1;
			}
		}
		if (flag == 0)//已经是有序的了,就不用再接着下一躺了,直接跳出
		{
			break;
		}
		end--;
	}
}

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

2.快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。
基本思想:(分治)
1.先从数列中取出一个数作为key值;
2.将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
3.对左右两个小数列重复第二步,直至各区间只有1个数。
过程:
将区间按照基准值划分为左右两半部分的常见方式有:

  1. 三数取中法
  2. 挖坑法
  3. 前后指针版本
    《快排三种版本详解》
    第一趟具体过程(最普通的划分:选第一个或最后一个元素做基准值):

Here Insert Picture Description
代码实现
普通版本实现:

//普通版本  注意:选最左边为基准值,必须从最右边开始作比较
int PartSort(int *a, int left, int right)
{
	int key = a[left];
	int start = left;
	while (left < right)
	{
		while (left < right && a[right]>=key)
		{
			right--;
		}
		while (left < right && a[left] <= key)
		{
			left++;
		}
		swap(&a[left], &a[right]);
	}
	swap(&a[left], &a[start]);
	return  left;
}
void QuickSort(int* a, int left, int right)
{
	//if (left >= right)//特殊情况  1 3 5 4, 当排第一趟时1为基准,end没找到比它小的一直减减,直到它本身了,此时left=right,返回keyindex为没动的left; 在递归下一趟时left为0,大于keyindex-1的-1,就不做左边无意义的递归趟数了   说明如果区域不存在或只有一个数据则不递归排序
	//	return;
	if (left < right)
	{
		if (left-right +1 < 10)//小区间优化:   区间内数据量小于一定值了,就没必要用快排的递归下去了,排序量小的数据还是可以用插入排序的
		{
			InsertSort(a+left, right-left + 1); //相当于也是传区间
		}
		else
		{
			int keyindex = PartSort3(a, left, right);
			// [left,keyindex-1]    keyindex   [keyindex+1,right]   二叉树结构  左区间 基准值 右区间  继续递归
			QuickSort(a, left, keyindex - 1);//基准值左边(大于它的)继续划分
			QuickSort(a, keyindex + 1, right);//基准值右边(小于它的)也继续
			//直到最小的都已经有序(划分好了)
		}
	}
}

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定
    缺点:在最坏的情况为初始记录就是排好序了的情况,此时时间复杂度为O(N^2),事实上,快速排序对初始记录很“乱”的情况下更有效。

归并排序

基本思路:
将数组分成二组 A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。
可以先将 A,B 组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
过程:
Here Insert Picture Description
递归实现代码:

//归并排序 先划分在合并
void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)  //一直分到每组只剩一个元素就停止划分,准备开始归并
	{
		return;
	}
	int mid = left + ((right - left) >> 1);
	_MergeSort(a, left, mid, tmp);     //先递归划分,再出栈合并
	_MergeSort(a, mid + 1, right, tmp);
	// [left, mid]
	// [mid+1, right]
	int i = left, j = mid;
	int x = mid + 1, z = right;
	int k = left;
	
	//将有二个有序数列a[left...mid]和a[mid...right]合并。
	while (i <= j && x<=z)
	{
		if (a[i] <=a[x])
		{
			tmp[k++] = a[i++];
		}
		else
		{
			tmp[k++] = a[x++];
		}
	}
	while(i <= j)
		tmp[k++] = a[i++];
	while(x <= z)
		tmp[k++] = a[x++];
	for (i = 0; i<k; i++)
		a[left + i] = tmp[i];
	//memcpy(a + left, tmp + left, sizeof(int)*(right - left+1 ));
}
// 归并排序递归实现
void MergeSort(int* a, int n)
{
	int* tmp = (int *)malloc(sizeof(int)*n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);

}

归并排序的特性总结:
归并排序的效率是比较高的,设数列长为 N,将数列分开成小数列一共要 logN 步,每步都是一个合并有序数列的过程,时间复杂度可以记为 O(N),故一共为 O(NlogN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在 O(NlogN) 的几种排序方法(快速排序,归并排序,堆排序)也是效率比较高的。

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

小结

综合分析和比较各种排序方法,可以得出以下结论:
1.简单排序法一般只用于n 较小的情况(例如n<30)。当序列中的记录“基本有序”时,直接插人排序是最佳的排序方法。如果记录中的数据较多,则应采用移动次数较少的简单选择排序法。
2.快速排序、堆排序和归并排序的平均时间复杂度均为0(nlogn),但实验结果表明,就平均时间性能而言,快速排序是所有排序方法中最好的。遗憾的是,快速排序在最坏情况下的时间
性能为0(n^2)。堆排序和归并排序的最坏时间复杂度仍为0( nlogn),当n较大时,归并排序的时间性能优于堆排序,但它所需的辅助空间最多。
3.可以将简单排序法与性能较好的排序方法结合使用。例如,在快速排序中,当划分子区间的长度小于某值时,可以转而调用直接插人排序法;或者先将待排序序列划分成若干子序列,分别进行直接插人排序,然后再利用归并排序法,将有序子序列合并成一一个完整的有序序列。
4.基数排序的时间复杂度可以写成0(dn)。因此,它最适用于n值很大而关键字的位数d较小的序列。当d远小于n时,其时间复杂度接近于0(n)。
5.从排序的稳定性上来看,在所有简单排序法(插入、简单选择、冒泡)中,简单选择排序是不稳定的,其他各种简单排序法都是稳定的。然而,在那些时间性能较好的排序方法中,希尔排序、快速排序、堆排序都是不稳定的,只有归并排序基数排序是稳定的。

综上所述,每一种排序方法各有特点,没有哪一种方法是绝对最优的。应根据具体情况选择合适的排序方法,也可以将多种方法结合起来使用。

发布了31 篇原创文章 · 获赞 8 · 访问量 2312

Guess you like

Origin blog.csdn.net/qq_44785014/article/details/104321735