参考:
https://www.cnblogs.com/onepixel/articles/7674659.html(写的比较好) 个人感觉这里面的4.希尔排序代码有些问题,少了一个循环
http://www.runoob.com/w3cnote/sort-algorithm-summary.html
https://www.cnblogs.com/RainyBear/p/5258483.html
《大话数据结构》
先放一张复杂图和稳定性的图
具体算法原理看之前的参考链接写的很清楚,之前只是看别人的代码,有些代码还是有些小问题的,以为自己会写,没有真正动手码,面试的时候手撕代码才发现自己写代码能力还远远不够,因此不要眼高手低,还是要多写,这里的重点在于写!代!码!
1.简单选择排序
基本思想:第i趟在后面n-i个待排的数据元素中找到最小元素的位置,记录下来,然后与第i个位置的元素进行交换。
#include <iostream>
using namespace std;
void selection_sort(int* array, int len)
{
int minIndex = 0;//最小数的索引
for (int i = 0; i < len; i++)
{
minIndex = i;//先把i认为是最小数的索引
for (int j = i + 1 ; j <len ; j++)
{
minIndex = array[j] < array[minIndex] ? j : minIndex; //找到最小数的索引
}
//swap
int tmp = array[i];
array[i] = array[minIndex];
array[minIndex] = tmp;
}
}
void main()
{
int a[] = { 5, 7, 0, 9, 2, 3, 1, 4 };
int len = sizeof(a) / sizeof(a[0]);//求数组长度
selection_sort(a,len);
for (int i = 0; i < len; i++)
{
cout << a[i] << endl;
}
getchar();
}
2.直接插入排序
基本思想:整个排序过程为n-1趟插入,第i趟插入需要找到插入的位置k,把k以后的元素位置依次往后移一个。
void insertion_sort(int* array, int len)
{
int tmp = 0;
int k = 0;
for (int i = 1; i < len; i++)
{
k = i; //待插入的位置
tmp = array[i];
for (int j = i-1; j>=0 && tmp<array[j]; j--)
{
array[j + 1] = array[j];//元素后移
k = j;//找到要插入的位置
}
array[k] = tmp;//插入元素
}
}
3.冒泡排序
基本思想:比较相邻的元素。如果第一个比第二个大,就交换它们两个;对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素就是最大的数。
void bubble_sort(int* array, int len)
{
int tmp = 0;
for (int i = 0; i < len; i++)
{
for (int j = len-1; j > i; j--)//从后往前找,小的数沉入湖底
{
if (array[j]<array[j-1])
{
tmp = array[j];
array[j] = array[j - 1];
array[j - 1] = tmp;
}
}
}
}
冒泡的改进:
引入辅助变量,如果循环过程中判断已经排好序了,就提前结束循环,能好一些。虽然最终的时间复杂度还是O(n^2)
void bubble_m_sort(int* array, int len)
{
int tmp = 0;
int exchange = 1; //引入一个辅助变量 1 认为是没排好序 0 认为已经排好序
for (int i = 0; i < len && exchange; i++)
{
for (int j = len - 1; j > i; j--)//从后往前找,小的数沉入湖底
{
exchange = 0;//一开始认为已经排好序了
if (array[j]<array[j - 1])
{
tmp = array[j];
array[j] = array[j - 1];
array[j - 1] = tmp;
exchange = 1;//进到if里面说明发生了交换,也就是说还没排好序
}
}
}
}
4.希尔排序
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。
基本思想:在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。
然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。
void shell_sort(int* array, int len)
{
int tmp = 0;
int k = 0;
int gap = len;
do
{
gap = gap / 3 + 1; //业界统一实验的,平均最好情况,经过若干次后收敛为1
for (int x = 0; x < gap; x++) // 根据增量分为若干子序列
{
for (int i = gap + x; i < len; i += gap)
{
k = i; //待插入的位置
tmp = array[i];
for (int j = i - gap; j >= 0 && tmp<array[j]; j -= gap)
{
array[j + gap] = array[j];//元素后移
k = j;//找到要插入的位置
}
array[k] = tmp;//插入元素
}
}
} while (gap>1);
}
5.快速排序
基本思想:(分治)
- 先从数列中取出一个数作为key值;
- 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
- 对左右两个小数列重复第二步,直至各区间只有1个数。
分两步骤:(1)partition (2)递归
辅助理解:挖坑填数
int partition(int* array, int left, int right) //分割左右两部分,并找到分割点位置
{
int i = left;
int j = right;
int key = array[left];
while (i<j) /*控制在当组内寻找一遍*/
{
while (i<j && array[j] >= key)//从后往前找,找到比key小的值,拿出来放到i位置上
{
j--;
}
array[i] = array[j];
while (i<j && array[i] <= key)//从前往后找,找到比key大的值,拿出来放到j位置上
{
i++;
}
array[j] = array[i];
}
array[i] = key;/*当在当组内找完一遍以后就把中间数key回归*/
return i;//把中间数key的位置返回
}
void quick_sort(int* array, int left, int right)
{
if (left>=right)/*如果左边索引大于或者等于右边的索引就代表已经整理完成一个组了*/
{
return;
}
int pos = partition(array, left, right);
quick_sort(array, left, pos - 1);//递归左部分
quick_sort(array, pos + 1, right);//递归右部分
}
6.归并排序
基本思想:(分治)
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
这样通过先递归的分解数列,再合并数列就完成了归并排序。
分两步骤:(1)分解 (2)合并
void merge(int* src, int* des, int low, int mid, int high)
{
int i = low;
int j = mid + 1;
int k = low;
while (i <= mid && j <= high)//把小的拿出来放进新的数组中
{
if (src[i] < src[j])
{
des[k++] = src[i++];
}
else
{
des[k++] = src[j++];
}
}
while (i <= mid)//如果还剩尾部几个元素,把剩余元素直接放进新数组中
{
des[k++] = src[i++];
}
while (j<=high)//如果还剩尾部几个元素,把剩余元素直接放进新数组中
{
des[k++] = src[j++];
}
}
void Msrot(int *src, int* des, int low, int high, int max)
{
if (low == high)// 只有一个元素,不需要归并,结果赋给des[low]
{
des[low] = src[low];
}
else
{
int mid = (low + high) / 2;
int* space = (int*)malloc(sizeof(int)*max);
//递归进行两路的划分,当剩下一个元素时,递归划分结束,然后开始merge归并操作
if (space!=NULL)
{
Msrot(src, space, low, mid, max);
Msrot(src, space, mid+1, high, max);
merge(space, des, low, mid, high);
}
free(space);
}
}
void merge_sort(int* array, int len)
{
Msrot(array, array, 0, len - 1, len);
}
7.堆排序
基本思想:参考《大话数据机构》,写的比较清楚完整
将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值) ,然后将剩余的 n - 1 个序列重新构造成一个堆,这样就刽寻到 n 个元素中的次小值。如此反复执行, 便能得到一个有序序列了 。
分两步骤:(1)构建初始大根堆(2)根节点与末尾元素交换,并调整其成为大根堆。
可以用两种方法进行堆调整:递归和非递归
void Swap(int A[], int i, int j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
//法1:用递归的方法调整
void Heapify(int A[], int i, int size) // 从A[i]向下进行堆调整
{
int left_child = 2 * i + 1; // 左孩子索引
int right_child = 2 * i + 2; // 右孩子索引
int max = i; // 选出当前结点与其左右孩子三者之中的最大值
if (left_child < size && A[left_child] > A[max])
max = left_child;
if (right_child < size && A[right_child] > A[max])
max = right_child;
if (max != i)
{
Swap(A, i, max); // 把当前结点和它的最大(直接)子节点进行交换
Heapify(A, max, size); // 递归调用,继续从当前结点向下进行堆调整
}
}
法2:用非递归的方法调整
//void Heapify(int A[], int i, int size) // 从A[i]向下进行堆调整
//{
// int tmp = A[i];
// for (int j = 2 * i + 1; j < size; j = j * 2 + 1)
// {
// if (j < size-1 && A[j+1] > A[j])
// {
// ++j;
// }
// if (tmp >= A[j])
// {
// break;
// }
// A[i] = A[j];
// i = j;
// }
// A[i] = tmp;
//}
int BuildHeap(int A[], int n) // 建堆,时间复杂度O(n)
{
int heap_size = n;
for (int i = heap_size / 2 - 1; i >= 0; i--) // 从每一个非叶结点开始向下进行堆调整
Heapify(A, i, heap_size);
return heap_size;
}
void HeapSort(int A[], int n)
{
int heap_size = BuildHeap(A, n); // 建立一个最大堆
while (heap_size > 1) // 堆(无序区)元素个数大于1,未完成排序
{
// 将堆顶元素与堆的最后一个元素互换,并从堆中去掉最后一个元素
// 此处交换操作很有可能把后面元素的稳定性打乱,所以堆排序是不稳定的排序算法
Swap(A, 0, --heap_size);
Heapify(A, 0, heap_size); // 从新的堆顶元素开始向下进行堆调整,时间复杂度O(logn)
}
}