今天来总结一下排序算法。排序算法有多种多样,而且也是面试中常见的考察点。排序算法分别有冒泡排序、插入排序,选择排序、希尔排序、归并排序、快排以及堆排序。
首先是冒泡排序,冒泡排序原理简单,每次都是两两比较,然后把大的放在后面,一直比较,直到把前M个元素中最大的放在最后面。也就是说经过一趟排序,最大的元素处在最后一位,再经过一趟排序,除最后一个元素之外的最大元素在倒数第二位。
1、从头开始元素两两比较,如果x>y,则交换x和y。
2、接着上面最大元素的位置继续和后面的元素比较,直到把最大的元素放在最后。
3、重复执行上面的两个步骤。
当然在经过一趟排序之后,最大的元素已经在最后了,在后面的排序中就不用考虑最后的那个元素了。
代码如下:
void bubbleSort(int* arr, int length){
if (arr== nullptr)
return;
for (int i=0; i<length-1; i++){ // 第几趟
for (int j=0; j<length-i-1; j++){ // 两两交换
if (arr[j]>arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
时间复杂度为:O(n^2)
空间复杂读为:O(n)
上述算法也可以把交换数据的代码单独拿出来,写成一个函数来调用。
第二是插入排序,插入排序就是从第一个元素开始,把它和它前面的元素逐个比较,直到找到比它小的元素,把它放在比它小的元素的后面。
1、从第一个位置取出元素get,把get和它前面的元素逐个进行比较。
2、直到找到比get的小的元素,把get放在比它小的元素的后面。
3、重复上述步骤,直到最后一个元素。
代码如下:
void InsertionSort1(vector<int>& a){
for (int i=1; i<a.size(); i++){//从第1个位置开始,每一个元素当一次get
int get = a[i];
int j = i-1;
while (j>=0 && a[j]>get){//和get前面的元素逐个比较
a[j+1] = a[j];
j--;
}
a[j+1] = get; //把get放在比它小的元素后面
}
}
注意:当把get放在选定位置时,应该是j+1, 因为当找到比get小的元素时,执行了一次j–
时间复杂度为:O(n^2)
空间复杂度为:O(n)
第三个为选择排序,选择排序是从当前乱序的数组中找出一个最小的元素,放在有序数组的最后面。
1、从头开始遍历数组,找到数组中最小的元素,把这个元素与第一个元素交换。
2、再从第二个元素开始遍历,找到最小的一个元素,把这个元素与第二个元素交换。
代码如下:
void selectionSort(vector<int>& a){
for (int i=0; i<a.size()-1; i++){
int min = i;
for (int j=i+1; j<a.size(); j++){
if (a[j]<a[min])
min = j;
}
int tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
时间复杂度为:O(n^2)
空间复杂度为:O(n)
第四个是希尔排序从名字上看不出来特点,因为它是以发明者命名的。它的另一个名字是“递减增量排序算法“。这个算法可以看作是插入排序的优化版,因为插入排序需要一位一位比较,然后放置到正确位置。为了提升比较的跨度,希尔排序将数组按照一定步长分成几个子数组进行排序,通过逐渐减短步长来完成最终排序。
代码如下:
void shell_sort(vector<int> &nums) {
for (int gap = nums.size() >> 1; gap > 0; gap >>= 1) { // times
for (int i = gap; i < nums.size(); i++) { // position
int temp = nums[i];
int j = i - gap;
for (; j >= 0 && nums[j] > temp; j -= gap) {
nums[j + gap] = nums[j];
}
nums[j + gap] = temp;
}
}
}
第五个是归并排序,归并排序其实就是一个分治的思路,把数组对半分,逐渐分到每一个数组只有一个元素的时候,再数组中两两比较,然后合并数组,变成每一个数组有两个元素的情况,依次类推即可。
代码如下:
void Merge(int arr[], int reg[], int start, int end) {
if (start >= end)return;
int len = end - start, mid = (len >> 1) + start;
//分成两部分
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
//然后合并
Merge(arr, reg, start1, end1);
Merge(arr, reg, start2, end2);
int k = start;
//两个序列一一比较,哪的序列的元素小就放进reg序列里面,然后位置+1再与另一个序列原来位置的元素比较
//如此反复,可以把两个有序的序列合并成一个有序的序列
while (start1 <= end1 && start2 <= end2)
reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
//然后这里是分情况,如果arr2序列的已经全部都放进reg序列了然后跳出了循环
//那就表示arr1序列还有更大的元素(一个或多个)没有放进reg序列,所以这一步就是接着放
while (start1 <= end1)
reg[k++] = arr[start1++];
//这一步和上面一样
while (start2 <= end2)
reg[k++] = arr[start2++];
//把已经有序的reg序列放回arr序列中
for (k = start; k <= end; k++)
arr[k] = reg[k];
}
void MergeSort(int arr[], const int len) {
//创建一个同样长度的序列,用于临时存放排序后的子树组
int reg[len];
Merge(arr, reg, 0, len - 1);
}
时间复杂度为:O(nlgn)
空间复杂度为:O(n)
第六个是快速排序,快速排序也是一种分治的思路,但是它并不是把数组分成一半一半的,而是从数组中选择一个基准数,然后经过一趟遍历数组,比基准数小的全部在一侧,比基准数大的全部在另一侧。然后再分别在基准数两侧的数组上实现排序。
第一种方法,是左右两个指针一块向中间动,代码如下:
void swap(int& num1, int& num2){
int tmp = num1;
num1 = num2;
num2 = tmp;
}
void quickSort1(int arr[], int left, int right){
if (left<right){
int key = arr[left], low = left+1, high = right;
while (low<=high){
while (low<high && arr[low]>key && arr[high]<key)
swap(arr[left++], arr[high--]);
if (arr[low]<=key) low++; // 此处不需要low<high如果加上了这句,永远跳不出while循环
if (arr[high]>=key) high--;
}
swap(arr[left], arr[high]);
quickSort1(arr, left, high-1);
quickSort1(arr, high+1, right);
}
}
上面有两处需要注意的地方,第一个是最外层的while循环,是low<=high,还有就是最后交换的是arr[left]和arr[high],因为此时high所指向的位置一定是小于key值的,low所指向的位置一定是大于key值的。所以我们要叫交换的就是arr[left]和arr[high]。
另一中写法比较简单,也是两个指针,只是这两个指针一个一个的动。代码如下:
void quickSort2(int arr[], int left, int right){
if (left<right){
int key = arr[left], low=left, high = right;
while (low<high){
while (low<high && key<=arr[high])
high--;
if (low<high)
arr[low++] = arr[high];
while (low<high && key>=arr[low])
low++;
if (low<high)
arr[high--] = arr[low];
}
arr[low] = key;
quickSort2(arr, left, low-1);
quickSort2(arr, low+1, right);
}
}
第七种为堆排序,堆排序其实是基于二叉树来操作的。按照层序遍历二叉树的顺序,以二叉树的最后一个非叶子节点开始,调整该节点及其子节点,使得该节点的元素值大于其两个子节点。然后再按照由下往上的层序遍历顺序,对上一个非叶子节点进行这样的调整,直到调整完根节点。这样整个二叉树都满足,根节点大于子节点的值。
接着再交换根节点和最后一个叶子节点的值,这样最大值就放在了最后。因为我们调整了二叉树的根节点,所以还有对二叉树进行调整,使其满足根节点大于叶子节点的情况。调整完,再把根节点和倒数第二个节点交换,以此类推即可。
代码如下:
void heapAdjust(int arr[], int beg, int length){//堆调整
int left = 2*beg+1;
while (left<length){
if (left+1<length && arr[left]<arr[left+1])
left++;
if (arr[beg]<arr[left]){
swap(arr[beg], arr[left]);
beg = left;
left = 2*beg+1;
}
else
break;
}
}
void heapSort(int arr[], int length){
for (int i=length/2-1; i>=0; i--){
heapAdjust(arr, i, length-1);
}
for (int i=length-1; i>0; i--){
swap(arr[0], arr[i]);
heapAdjust(arr, 0, i);
}
}
参考:
https://www.cnblogs.com/MOBIN/p/5374217.html
http://www.runoob.com/w3cnote/sort-algorithm-summary.html
http://yansu.org/2015/09/07/sort-algorithms.html