Algorithm Review 1 基础排序算法
来源:互联网 发布:dnf阿里云搭建 编辑:程序博客网 时间:2024/05/01 04:21
整天做上层架构设计和写界面,把计算机最重要的算法与数据结构都忘得差不多了。所以从这篇开始系统地复习常见的算法与数据结构,这里会暂时抛弃Java,用C++来做,因为即使是Android系统,算法实现也大多是通过C或者C++编译成so来实现的。对于算法的描述我会尽量抛弃复杂的理论描述,尽量用大白话来让大家好理解。
第一篇是关于普通数组排序的,默认排序都是从小到大~
一、冒泡排序
算法复杂度n^2
把数组竖起来,尾部想象成一个水面,每次让最大的元素浮到水面上,就像冒泡一样。
基本思想就是每次两两数据比较,如果左边元素大于右边元素,则交换两者,这样每次都能让一个最大的元素浮上“水面”。
void bubbleSort(int arr[], int length){ for(int i = 0; i < length; i++) { for(int j = 0; j < length - i -1; j++) { if(arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }}
注意:C++内置有swap函数,因为涉及到内存的读写所以效率较低,有兴趣的同学可以用同样的算法进行测试,在数据量超过100000时,算法效率会大幅下降,所以本文中不使用该函数。
二、选择排序
算法复杂度n^2
假设每次数组的第一个元素的索引是指向的最小的元素,然后用这个元素和后面所有的元素进行比较,如果后面元素有比这个元素小,就把这个假设的索引值改成这个较小元素的索引,让后继续往后搜索直到数组末尾,每次选出来的就是那个最小的元素的索引,然后让第一个索引和这个索引交换数据即可。
void selectionSort(int arr[], int length){ for(int i = 0; i < length; i++) { int minIndex = i; for(int j = i + 1; j < length; j++) { if(arr[j] < arr[minIndex]) { minIndex = j; } } if(minIndex != i) { int temp = arr[minIndex]; arr[minIndex] = arr[i]; arr[i] = temp; } }}
三、插入排序
算法复杂度n^2
每次将后面一个元素插入到前面已经排序好的数组的正确位置,所以插入排序是从i = 1开始的,因为只有一个元素的时候它本身就是有序的。
void insertionSort(int arr[], int length){ for (int i = 1; i < length; i++) { for (int j = i - 1; j >= 0; j--) { if(arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); } } }}void insertionSortImprove(int arr[], int length){ for (int i = 1; i < length; i++) { int temp = arr[i]; int pos = i; for (int j = i - 1; j >= 0; j--) { if(arr[j] > temp) { arr[pos] = arr[j]; pos = j; } } arr[pos] = temp; }}
注意这里特地写了两种方法,第一种是直接调用C++提供的swap函数,需要进行内存的操作,而第二种则使用了我们前面所述的方法。虽然swap看上去代码可读性更好,但是因为效率的问题,方法1的运行时间会比方法2多出几倍。后面我会写专门的测试用例来验证这一点。
四、归并排序
算法复杂度nlogn
归并排序是比较经典的用空间换时间,是分治算法的一种典型运用,同时还用到了递归。归并算法很难用语言描述清楚,这里上两张图。
每次把数组进行划分,直到只有一个元素时数组就是有序的了,这时候递归返回,对Level2进行归并,这时候Level2就是有序的了,再返回,对Level1进行归并,以此类推。
归并的具体做法可以看下面这张图:
每次需要开辟一片新的内存来保存原来的数组数据,然后需要三个索引来进行操作,k每次的取值为x,y中较小的那个,同时需要考虑越界的问题,具体还是看代码来理解吧。
void insertionSort(int arr[], int l , int r){ for(int i = l + 1; i <= r; i++) { int temp = arr[i]; int pos = i; for(int j = i - 1; j >= l; j--) { if(arr[j] > temp) { arr[pos] = arr[j]; pos = j; } } arr[pos] = temp; }}void doMerge(int arr[], int l, int mid, int r){ int aux[r - l + 1]; for(int i = l; i <= r; i++) { aux[i - l] = arr[i]; } int x = l; int y = mid + 1; for(int k = l; k <= r; k++) { if(x > mid) { arr[k] = aux[y - l]; y++; } else if(y > r) { arr[k] = aux[x - l]; x++; } else if(aux[x - l] < aux[y - l]) { arr[k] = aux[x - l]; x++; } else { arr[k] = aux[y - l]; y++; } }}//闭区间[l, r]void realMergeSort(int arr[], int l, int r){ if(r - l <= 15) { insertionSort(arr, l, r); return; } int mid = (l + r) / 2; realMergeSort(arr, l, mid); realMergeSort(arr, mid + 1, r); if(arr[mid] > arr[mid + 1]) { doMerge(arr, l, mid, r); }}void mergeSort(int arr[], int length){ realMergeSort(arr, 0, length - 1);}
注意这里在代码里还加入了两点优化:
1、当归并排序的数据区间小于某个值时,我们可以使用插入排序来替代归并。
2、只有arr[mid] > arr[mid + 1]时,才需要进行归并。
五、快速排序
算法复杂度nlogn
快速排序的思想也是分治思想+递归的一种典型实现,标准的快速排序就是把数组分成大于v和小于v的两部分,v一般取数组的第一个元素,分组成功后分别对大于v和小于v的部分继续进行快速排序。可看图参考:
可见这里需要三个索引,其中j记录的就是v最终应该所在的位置。
int partition(int arr[], int l, int r){ int v = arr[l]; int j = l; for(int i = l + 1; i <= r; i++) { if(arr[i] < v) { swap(arr[j + 1], arr[i]); j++; } } swap(arr[l], arr[j]); return j;}void realQuickSort(int arr[], int l, int r){ if(l >= r) { return; } int pos = partition(arr, l, r); realQuickSort(arr, l, pos - 1); realQuickSort(arr, pos + 1, r);}void quickSort(int arr[], int length){ realQuickSort(arr, 0, length - 1);}
标准的快排在面对基本有序的数组时性能会急剧下降,我们可以想见在上面的算法时其实我们对等于v的部分没有进行任何处理,所以对于基本有序的数组,这样的做法其实有大量的排序是做了无用功,这才有了更好的三路快速排序,上个图大概演示一下:
lt指向小于v的部分,gt指向大于v的部分,可见如果等于v的元素很多的时候,将大大减少算法的排序次数。
void realQuickSort3Ways(int arr[], int l, int r){ if( r - l <= 15 ) { insertionSort(arr, l, r); return; } swap( arr[l], arr[rand() % (r-l+1) + l]); int v = arr[l]; int i = l + 1; int lt = l; int gt = r + 1; while(i < gt) { if(arr[i] > v) { swap(arr[i], arr[gt - 1]); gt--; } else if(arr[i] < v) { swap(arr[i], arr[lt + 1]); lt++; i++; } else { i++; } } swap(arr[l], arr[lt]); realQuickSort3Ways(arr, l, lt - 1); realQuickSort3Ways(arr, gt, r);}void quickSort3Ways(int arr[], int length){ srand(time(NULL)); realQuickSort3Ways(arr, 0, length - 1);}
注意这里依然加入了两个优化,一个是前面提过的当数据量较小且基本有序时,插入排序是效率最高的,第二个是对数组进行一定的随机排序也可以提高快排的效率,这里涉及复杂的数学证明,就不赘述了。
基础排序的基本技巧就是控制好几个临界点,然后理解算法本身的执行流程,代码写起来才会行云流水。
先到这,这篇拖了好久了~
- Algorithm Review 1 基础排序算法
- Algorithm Review 2 堆排序
- 排序算法review<1>--直接插入排序
- (Basic algorithm学习笔记)《基础算法一》- 排序
- sorting algorithm排序算法
- Algorithm--排序算法总结
- [Algorithm]排序算法
- 排序算法-Sorting algorithm
- 【algorithm】排序算法
- 【Algorithm】基础算法学习
- Encryption review-part 1 Symmetric-key algorithm
- 排序算法review<2>--Shell 排序
- 算法库algorithm-1-algorithm
- (C#)排序算法 Sort Algorithm
- 【Algorithm】快速排序--算法实现
- C++语法基础--泛型算法(generic algorithm)--对容器排序的算法sort(),stable_sort(),unique()
- Algorithm Review: Sorting
- 【算法】插入排序 insert sort algorithm
- flask蓝图的使用
- [BZOJ1391][Ceoi2008]order(最小割)
- BZOJ 1084: [SCOI2005]最大子矩阵 DP
- 蓝桥杯 算法训练 输出米字形
- Ubuntu 16.04 LTS与windows双系统时间同步解决方法
- Algorithm Review 1 基础排序算法
- 使用JPA实现乐观锁
- flask部署-Ubuntu下使用nginx+uwsgi+supervisor部署flask应用
- NUCLEO-F767ZI以太网初探
- 蓝桥杯 基础练习 数的读法
- 多客户端通信之Select服务器
- 原来微信收藏也是有容量的
- Git学习之路(2)-安装GIt和创建版本库
- React入门笔记