数据结构的七种排序
来源:互联网 发布:php后端面试题 编辑:程序博客网 时间:2024/06/06 08:39
七种排序
从大类上可以分为插入排序、选择排序、交换排序和归并排序
插入排序: 插入排序、希尔排序
选择排序:选择排序、堆排序
交换排序:冒泡排序、快速排序
归并排序: 归并排序
1、插入排序
插入排序通过将一个元素插入一个按照大小排序好的序列这一过程,来实现对所有元素的排序。
时间复杂度:O(N^2)
插入时的两种情况:
1)找到了比tmp小的元素下标end,然后将tmp插入end下标的后面
2)直到end<0时,说明tmp是最小的元素,则将tmp插入第一个位置
代码实现:
//插入排序template<class T>void InsertSort(T* a,int n){for (int i = 0; i < n-1; ++i){int end = i;int tmp = a[end+1];for (end = i; end >= 0; --end){if (a[end] > tmp){a[end + 1] = a[end];}else{break;}}a[end + 1] = tmp;}}
2、希尔排序
对一组数据进行多次分组(gap为每组相邻两元素的间距)预排序,然后再进行一次插入排序。希尔排序是对插入排序的优化。
原理:是通过预排序将小的元素尽可能较快的移至前面。最后通过 一次插入排序最终整合
时间复杂度:O(N)~O(N^2)
问:如何确定gap的值?
刚开始定义gap=n即等于元素的个数,
单趟预排序通过gap=gap/3+1;来决定每次循环gap的值
gap>1时进行预排序,gap==1时进行插入排序整合。
注意:预排序不是将每组分开排序,而是将所有组穿插着进行排序
代码实现:
//希尔排序void ShellSort(int *a, int n){int gap = n;while (gap > 1) //进行多次预排序{gap = gap / 3 + 1;//gap>1时进行预排序 gap==1时进行最后一次插入排序//预排序多组间交替排序(即一次循环可以排多组)for (int i = 0; i < n - gap; ++i){int end = i;int tmp = a[end + gap];for (end = i; end >= 0; end -= gap){if (a[end] > tmp){a[end + gap] = a[end];}else{break;}}a[end + gap] = tmp;}}}
3、选择排序
选择排序是每次从某个集合里选出最大或最小的一个元素,每次可以确定一个元素的位置,将集合不断缩小,最终完成对整个集合的排序。
时间复杂度:O(N^2)
优化版:每次可以取出最大值和最小值,确定两个位置,提高一倍效率
注意:
优化版本可能会出现记录最大值的下标(max)在集合最左端(begin),记录最小值的下标(min)在集合最右端(end),出现交换两次后又将两元素交换回去的情况
解决方法:
当将min下标的元素和begin下标的元素进行交换后,将max变量更新为min,加一条判断即可(详情见代码实现)
代码实现:
//选择排序//优化版选择排序---每次选择两个(最大和最小)void SelectSort(int *a, int n){int begin = 0;int end = n - 1;while (begin < end){int max = begin;int min = begin;for (int i = begin; i <= end; ++i){if (a[i] > a[max]){max = i;}if (a[i] < a[min]){min = i;}}swap(a[min], a[begin]);if (max == begin) //解决max==begin且min==end造成交换两次的情况{max = min;}swap(a[max], a[end]);begin++;end--;}}
4、堆排序
通过建堆和堆算法来实现多一个集合的排序
时间复杂度:O(N*lgN)
堆排序过程:
1、建大堆
2、将堆顶元素和堆的最后一个元素交换
3、堆顶向下调整
4、将最后一个元素去除在堆外(但不是真正的删除)
代码实现:
void AdjustDown(int *a, int root, int end) //向下调整{int parent = root;int child = parent * 2 + 1;while (child <= end){if (a[child] < a[child + 1] && (child + 1) <= end){++child;}if (a[parent] >= a[child]){break;}swap(a[parent], a[child]);parent = child;child = parent * 2 + 1;}}void HeapSort(int *a, int n){int end = n-1; //end是数组最后一个元素下标//建大堆for (int i = (end - 1) / 2; i >= 0; --i){AdjustDown(a,i,end);}//交换元素和调整while (end){swap(a[0],a[end]);--end; //每次将交换后最大的元素隐藏AdjustDown(a,0,end);}}
5、冒泡排序
通过相邻的元素进行交换排序,每次循环可以确定一个最大或最小的元素,然后缩小集合范围,直至集合有序。
优化:可以增加一个标志位flag,当序列已经有序时不用再进行多余的比较
时间复杂度:O(N^2)
代码实现:
//冒泡排序void BubbleSort(int *a,int n){for (int i = n-1; i > 0; --i){bool flag = 0; //加标志位提高效率(已经有序时直接退出)for (int j = 0; j < i; ++j){if (a[j + 1] < a[j]){swap(a[j + 1], a[j]);flag = 1;}}if (flag == 0) //表示上次一单趟比较没有进行交换--可以直接结束排序{break;}}}
6、快速排序
找出一个键值key,将集合中小于key的值放一边,大于key的值放在另外一边,即一次排序可以确定key值的准确位置,然后递归排序key的左边和key的右边,直至集合有序。
时间复杂度:O(N*lgN)
空间复杂度:O(lgN)
快排的时间复杂度最坏情况的O(N^2),但快排可以通过各种优化保证时间复杂度不会到最坏,所以认为快排的时间复杂度为O(N*lgN)。
快排的单趟排序方法:
1)左右指针法、挖坑法,两种方法原理基本一致(见代码)。
2)前后指针法:通过两个指针走的快慢不同将单趟集合元素分出大于key和小于key两个区间,具体实现见代码。
快排在时间复杂度上的优化:
1)三数取中法:对key值的选取一般为最右端的值,但最右端得值若是最大或最小值,则此次单趟排序没意义,故取最左端、中间、最右端三个值当中的中间大小的值与最右端值进行交换,可以保证每趟排序不会出现没有元素交换的现象。
2)小区间优化法:当快排递归到比较小的区间进行排序时,可选用插入排序等排序方式代替快排,减小空间和调用函数栈帧时间上的开销。
非递归实现快排:
用栈结构模拟递归的调用过程实现,每次单趟排序从栈里取需要排序的区间,并将单趟排序得到的两个小区间分别入栈,等待下次出栈排序,直至栈为空排序结束
代码实现:
//快速排序//[left,right]闭区间int PartSort(int *a, int left, int right) //单趟排序{//int key = a[right]; //挖坑法int key = right; //左右指针法while (left < right){while (left < right && a[left] <= a[key]){left++;}//a[right] = a[left];while (left < right && a[right] >= a[key]){right--;}//a[left] = a[right];swap(a[left],a[right]);}swap(a[left], a[key]);//a[left] = key;return left;}int PartSort2(int *a, int begin, int end) //前后指针法{int cur = begin;int prev = cur - 1;while (cur < end){if (a[cur] < a[end] && ++prev != cur){swap(a[cur], a[prev]);}cur++;}swap(a[++prev], a[end]);return prev;}void QuickSort(int *a, int begin, int end){if (begin >= end){return;}if (end - begin < 5) //优化---对深层次递归优化为其他排序方式(减少空间开销){InsertSort(a + begin, end - begin + 1);}else{int mid = PartSort2(a, begin, end);QuickSort(a, begin, mid - 1);QuickSort(a, mid + 1, end);}}//非递归快排void QuickSortNonR(int *a, int begin, int end){stack<int> s;s.push(end);s.push(begin);while (!s.empty()){int left = s.top();s.pop();int right = s.top();s.pop();int mid = PartSort(a, left, right);if (left < mid - 1) //当区间只有一个元素进行排序时不用入栈{s.push(mid - 1);s.push(left);}if (mid + 1 < right){s.push(right);s.push(mid + 1);}}}
7、归并排序
将两段有序区间归并为一段有序的区间,但归并排序在空间上会有消耗(开辟同等大小的缓冲区),更适合外排序,
时间复杂度:O(N*lgN)
空间复杂度:O(N)
代码实现:
//归并排序void _Merge(int *a, int *tmp, int begin, int mid_left, int mid_right, int end){int tmp_begin = begin;int left_begin = begin;int left_end = mid_left;int right_begin = mid_right;int right_end = end;if (left_begin >= right_end)return;while (left_begin <= left_end && right_begin <= right_end){if (a[left_begin] > a[right_begin]){tmp[tmp_begin++] = a[right_begin++];}else{tmp[tmp_begin++] = a[left_begin++];}}if (left_begin > left_end){memcpy(tmp + tmp_begin, a + right_begin, sizeof(a[0]) * (right_end - right_begin + 1));}else{memcpy(tmp + tmp_begin, a + left_begin, sizeof(a[0]) * (left_end - left_begin + 1));}memcpy(a + begin, tmp + begin, sizeof(a[0]) * (end - begin + 1));}void MergeSort(int *a, int *tmp, int begin, int end){if (begin >= end){return;}int mid = begin + ((end - begin) >> 1);MergeSort(a, tmp, begin, mid);MergeSort(a, tmp, mid + 1, end);_Merge(a, tmp, begin, mid, mid + 1, end);}
对各种排序的比较:
时间复杂度都为N^2的三个排序里,效率高低的相对排序(最好情况)
插入排序 > 冒泡排序 > 选择排序
稳定的排序(三种):
插入排序、冒泡排序、归并排序
- 数据结构的七种排序
- 数据结构经典的七种排序方法
- [数据结构]七种排序算法小结
- 数据结构中的七种排序算法介绍
- (数据结构)七种常用的排序算法分析及代码实现(上)
- 七种排序的实现
- (数据结构)七种常用的排序算法分析及代码实现(下)——快速排序及归并排序
- 数据结构与算法之七归并排序
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- 数据结构实验之排序七:选课名单
- jQuery获取窗口大小 及窗口大小改变时执行代码
- 数据结构——顺序表
- 如果移动一个精灵,让它也到你手指移动到的位置
- 华为OJ——配置文件恢复
- HTTP响应报文
- 数据结构的七种排序
- AK消防 注册消防工程师 要点汇总 建筑消防性能化防火设计
- 点击按钮(Button、MenuItemSprite)
- Linux C++ 实现简易小型的Web服务器httpd
- lambda expressions are not supported at this language level解决方法
- HTML学习思维导图
- HTML DOM知识框架
- Go 1.9 以后 map 并发读写的问题,sync.Map 揭秘
- MySQL的冰山一角