常见排序算法的实现(归并排序、快速排序、堆排序、选择排序、插入排序、希尔排序)
2016-05-16 22:27
1091 查看
这篇博客主要实现一些常见的排序算法。例如:
//冒泡排序
//选择排序
//简单插入排序
//折半插入排序
//希尔排序
//归并排序
//双向的快速排序
//单向的快速排序
//堆排序
对于各个算法的实现原理,这里不再多说了,代码中注释较多,结合注释应该都能理解算法的原理,读者也可自己google一下。另外,注释中有很多点,比如边界条件、应用场景等已经用 * 标记,* 越多,越应该多注意。
下面是实现:
常见的排序就是这么多了。。。
下面附上一张各种排序的时间复杂度,空间复杂度,以及稳定性的比较:
//冒泡排序
//选择排序
//简单插入排序
//折半插入排序
//希尔排序
//归并排序
//双向的快速排序
//单向的快速排序
//堆排序
对于各个算法的实现原理,这里不再多说了,代码中注释较多,结合注释应该都能理解算法的原理,读者也可自己google一下。另外,注释中有很多点,比如边界条件、应用场景等已经用 * 标记,* 越多,越应该多注意。
下面是实现:
//冒泡排序 void BubbleSort(int *arr, int n) { if(NULL == arr || n < 2) return ; for(int i = 0; i < n; ++i) //i 趟数 { for(int j = 0; j < n-i-1; ++j) //j 第i趟需要比较的次数, 一定是从头开始比较,因为最后一个元素已经有序 { if(arr[j] > arr[j+1]) //* { std::swap(arr[j], arr[j+1]); //如果前面的一个元素较大,就交换 } } } }
//选择排序 void SelectSort(int *arr, int n) { if(NULL == arr || n < 2) return ; for(int i = 0; i < n; ++i) //每次从当前位置往后找一个最小的值,放在当前位置 { int minIndex = i; //找最小值时用来记录下标 for(int j = i; j < n; ++j) //从i位置开始往后 找到一个最小值 { if(arr[j] < arr[minIndex]) { minIndex = j; //找到比arr[i]小的时候,记录最小值下标 } } if(minIndex != i) { std::swap(arr[i], arr[minIndex]); //把最小值放到i位置 } } }
//简单插入排序 void SimpleInsertSort(int *arr, int n) { if(NULL == arr || n < 2) return ; int i = 0, j = 0; int save = 0; for(i = 1; i < n; ++i) //默认第一个元素已经有序,从第二个元素开始,把每个元素插入到前面有序的合适位置 { save = arr[i]; //保存当前的值,如果移动元素,可能会被覆盖 for(j = i-1; j >= 0; j--) { if(arr[j] < save) break; arr[j+1] = arr[j]; //移动元素 } if(j+1 != i) arr[j+1] = save; //在合适位置放上之前保存的数 } }
//折半插入排序 void BinaryInsertSort(int *arr, int n) { if(NULL == arr || n < 2) return ; int i = 0, j = 0; int save = 0; for(i = 1; i < n; ++i) { save = arr[i]; int low = 0, high = i - 1; int mid = 0; while(low <= high) //找一个位置 放要插入的数, 循环结束后,low > high { //折半插入比简单插入的优点就在这里,能很快找到要插入的位置,减少了比较次数 mid = low + (high-low)/2; if(arr[mid] < save) low = mid + 1; //else if(arr[mid] > save) else high = mid - 1; } for(j = i; j > low; --j) //移动元素,arr[low]是比save大的第一个数字,拷贝到这个数为止,并不能减少移动元素的次数 { arr[j] = arr[j-1]; } arr[j] = save; //arr[low]放上save } }
//希尔排序 //严蔚敏版本,实现的不太好,里面自己指定了 步长(用step数组存储,一定要保证数组最后一个元素为1, 原因下面有解释) //void ShellInsert(int *arr, int n, int gap) //这个函数说白了就是插入排序,只不过是把插入排序中的步长1换成了gap //{ // assert(arr); // // int i = 0, j = 0; // int save = 0; // for(i = 0+gap; i < n; i+=gap) //每隔一个步长的所有数做一次插入排序 // { // save = arr[i]; // for(j = i-gap; j >= 0; j-=gap) //找个合适的位置放待插入的数 // { // if(arr[j] < save) // break; // arr[j+gap] = arr[j]; // } // if(j+gap != i) // arr[j+gap] = save; //放数 // } //} // //void ShellSort(int *arr, int n, int *step, int t) //step里面存放的是每次希尔排序的步(t是step的长度), step一定是降序排列的,最后一个步长一定为1 //{ // if(NULL == arr || NULL == step) // return ; // // for(int i = 0; i < t; ++i) // { // ShellInsert(arr, n, step[i]); //每次找一个步长进行插入排序 // } //} void ShellSort(int *arr, int len) { assert(arr && len>0); int gap = len; //gap是每次希尔插入的步长 while(gap > 1) { gap = gap/3 + 1; //最后加1能保证gap的最后一个值一定是1,因为之前gap大于1的过程都是为最后一个简单插入做准备(称为预处理) int cur = gap; for(cur = gap; cur < len; ++cur) //下面就是简单插入排序,只不过插入排序的步长为gap { int tmp = arr[cur]; int findIndex = cur - gap; while(findIndex >= 0 && arr[findIndex] > tmp) { arr[findIndex+gap] = arr[findIndex]; findIndex -= gap; } arr[findIndex+gap] = tmp; } } }
//归并排序 void Merge(int *arr, int begin, int mid, int end) // 把arr中的 [begin, mid]、 [mid+1, end] 两个有序片段排成一个有序序列 { assert(arr); int *brr = new int[end+1-begin]; //临时数组,用来保存临时有序序列 int i = 0, j = 0; //两个有序片段的指针 int k = 0; for(i = begin, j = mid+1; i<=mid && j<=end; ) { if(arr[i] <= arr[j]) //如果两个数相等,默认先放前面一段的数i指向的片段,这也保证了归并排序是稳定的 brr[k++] = arr[i++]; else brr[k++] = arr[j++]; } while(i <= mid) brr[k++] = arr[i++]; //如果子数组还有剩余元素没有插入到临时数组中,直接拷贝全部元素到临时数组中 while(j <= end) brr[k++] = arr[j++]; for(i = begin; i <= end; ++i) //转移临时数组到原数组中 *** arr[i] = brr[i-begin]; delete []brr; //释放临时数组 }
//双向的快速排序 int partition(int *arr, int begin, int end) //[begin, end] *** { int key = arr[begin]; //枢轴默认取第一个元素 int save = arr[begin]; int left = begin, right = end; while(left < right) { while(left<right && arr[right] >= key) --right; arr[left] = arr[right]; while(left<right && arr[left] <= key) ++left; arr[right] = arr[left]; } arr[left] = save; //left是枢轴元素所在处,left前面的元素都比arr[left]小,后面的元素都比arr[left]大 return left; } //双向的快速排序,需要前后指针往中间遍历 void QuickSort_TwoWay(int *arr, int begin, int end) // [begin, end] { if(NULL == arr || begin >= end) return ; if(begin < end) { int partiIndex = partition(arr, begin, end); QuickSort_TwoWay(arr, begin, partiIndex-1); // [begin, partiIndex-1] QuickSort_TwoWay(arr, partiIndex+1, end); // [partiIndex+1, end] } }
//单向的快速排序,只需要一个指针从前往后扫描, 该方法特别适合链表的排序 ****** void QuickSort_OneWay(int *arr, int begin, int end) // [begin, end] { if(NULL == arr || begin >= end) return ; int index = begin+1; //往后找比key小的数****** int key = arr[begin]; //相当于枢轴 int mid = begin; //相当于partition,用于标记左右有序的分界****** for(index=begin+1 ; index <= end; ++index) { if(arr[index] < key) //找小 { if(++mid != index) //防止自己跟自己交换 std::swap(arr[index], arr[mid]); } } std::swap(arr[begin], arr[mid]); //mid位置处放入枢轴 QuickSort_OneWay(arr, begin, mid-1); //递归排序左半部分 QuickSort_OneWay(arr, mid+1, end); //递归排序左半部分 }
//堆排序 方法1:常规情况 void AdjustDown(int *arr, int len, int root) //调整以root为根的子树满足堆的特点(这里实现的是大堆, 下面还有优化) { int parent = root; int child = 2*root + 1; //默认root的左子树比右子树大 while(child < len) { if(child+1 < len && arr[child+1] > arr[child]) //如果右子树大,调整child ++child; if(arr[parent] < arr[child]) //如果子树比根节点大,交换 { std::swap(arr[parent], arr[child]); parent = child; //交换完成后,以child为根的子树可能不满足堆的特点,需要向下重新调整(AdjustDown) child = 2*parent + 1; } else break; } } void HeapSort(int *arr, int len) { assert(arr && len>0); for(int root = len/2-1; root >= 0; --root) //建堆 len/2 - 1是第一个非叶子节点的下标 { AdjustDown(arr, len, root); //从第一个非叶子节点一直调整到根节点(下标为0) } for(int i = 0; i < len-1; ++i) { std::swap(arr[0], arr[len-1-i]); //根节点是最大的元素,根节点和最后一个元素交换,最大元素处于最后 AdjustDown(arr, len-1-i, 0); //重新调整树满足堆,但是节点个数要减1(因为最后一个元素已经有序) } } //堆排序 方法2:使用仿函数+模板函数 template<class T> class Great { public: bool operator() (const T& left, const T& right) //对象重载了(), 可以像函数一样使用,例如: great(3, 1) 返回true { return left > right; } }; template<class T> class Less { public: bool operator() (const T& left, const T& right) { return left < right; } }; //*** //该方法实现为模板函数,通过给函数传进一个Great或Less的对象,从而动态实现大堆或小堆 template<class Compare> void AdjustDown(int *arr, int len, int root, Compare com) //用法 AdjustDown(arr, len, root, Great<int>()) { int parent = root; int child = 2*root + 1; while(child < len) { if(child+1 < len && com(arr[child+1], arr[child])) ++child; if(com(arr[child], arr[parent])) { std::swap(arr[parent], arr[child]); parent = child; child = 2*parent + 1; } else break; } } //堆排序 方法3:使用仿函数+模板类 template<class T, template<typename T> class Compare = Less > class Heap { public: Heap(T *arr, int sz) :_arr(arr) ,_size(sz) { //建堆 for(int root = _size/2 - 1; root >= 0; --root) AdjustDown(_size, root); } ~Heap() { //神马都不用做 } //功能同上面的AdjustDown void AdjustDown(int len, int root) { Compare<T> com; int parent = root; int child = 2*parent + 1; while(child < len) { if(child+1 < _size && com(_arr[child+1], _arr[child])) ++child; if(com(_arr[child], _arr[parent])) { std::swap(_arr[parent], _arr[child]); parent = child; child = 2*parent + 1; } else break; } } void PrintArray() { for(int i = 0; i < _size; ++i) cout<<_arr[i]<<" "; cout<<endl; } //protected: public: T *_arr; int _size; }; template<class T> void HeapSort(T *arr, int len) { Heap<T, Great> hp(arr, len); for(int i = 0; i < len; ++i) { std::swap(hp._arr[0], hp._arr[len-1-i]); hp.AdjustDown(len-1-i, 0); } }
常见的排序就是这么多了。。。
下面附上一张各种排序的时间复杂度,空间复杂度,以及稳定性的比较:
相关文章推荐
- Android Surface View绘图API详解
- pdo的三个预定义类,PDO PDOStatement PDOException
- 所有偷过的懒,都会变成打脸的巴掌
- [javaSE] 数组(排序-选择排序)
- Kubernetes 安装配置笔记
- Android开发工具之Android Studio---版本控制SVN使用(二)
- BZOJ_1269_文本编辑器_[AHOI2006]_(Spaly)
- javascript:js脚本的3中引入方法
- No TypeTag available for person
- Centos 7 安装VNC步骤
- leetcode.10. Regular Expression Matching
- [动态规划]之裸lis之最长上升子序列POJ 2533
- Java中补码的规则
- osg布告板技术(Billboard)
- getRequestDispatcher ,sendRedirect
- 测试并发编程demo
- 求成绩
- hdoj 5681 zxa and wifi
- MySQL:Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEM
- shell脚本的使用---if变量编写lamp管理脚本