经典排序算法
1. 冒泡排序
- 元素进行两两比较,若第一个比第二个大,则交换它们的值;
- 执行比较和交换(n-1次),直到到达数组的最后一个元素,冒出的元素则为数组中的最大值;
- 重复1,2步骤
//进行冒泡操作
void Bubble(int arr[], int n)
{
int temp;
for(int i = 0; i < n-1; i++)
{
if(arr[i] > arr[i+1])
{
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
}
//冒泡排序
void BubbleSort(int arr[], int n)
{
for(int i = n; i > 0; i--)
{
Bubble(arr, i);
}
}
2.选择排序
- 先在数据中找出最大或最小的元素,放到序列的起始;
- 然后再从余下的数据中继续寻找最大或最小的元素,依次放到排序序列中,直到所有数据样本排序完成
// 找到最大值所在的位置
int findmax_Pos(arr[], int n)
{
int pos = 0;
int max = arr[0];
for(int i = 0; i < n; i++)
{
if(arr[i] > max)
{
max = arr[i];
pos = i;
}
return pos;
}
}
//选择排序
void selectSort(arr[], int n)
{
int maxpos, temp;
for(int i = n; i > 0; i-- )
{
maxpos = findmax_Pos(arr, i);
temp = arr[maxpos];
arr[maxpos] = arr[i-1];
arr[i-1] = temp;
}
}
3.插入排序
- 先将待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;
- 然后从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置,直到所有数据都完成排序;如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。
void InsertSort(int a[], int n)
{
for (int j = 1; j < n; j++)
{
int key = a[j]; //待排序第一个元素
int i = j - 1; //代表已经排过序的元素最后一个索引数
while (i >= 0 && key < a[i])
{
//从后向前逐个比较已经排序过数组,如果比它小,则把后者用前者代替,
//其实说白了就是数组逐个后移动一位,为找到合适的位置时候便于Key的插入
a[i + 1] = a[i];
i--;
}
a[i + 1] = key;//找到合适的位置了,赋值,在i索引的后面设置key值。
}
}
4.希尔排序
希尔排序,也称递减增量排序算法,实质是分组插入排序。由 Donald Shell 于1959年提出。希尔排序是非稳定排序算法。
- 先将整个数据序列分割成若干子序列分别进行直接插入排序;
- 待整个序列中的记录基本有序时,再对全部数据进行依次直接插入排序
void Shell_Sort(int arr[], int n)
{
for(int gap = n/2; gap>0; gap /= 2)
{
for(int i = 0; i < gap; i++)
{
for(int j = i+gap; j < n; j +=gap)
{
//对每个分组进行插入排序
if(arr[j-gap]>arr[j])
{
int temp = a[j];
k = j - gap;
while (k>=0 && a[k]>temp)
{
a[k+gap] = a[k];
k -= gap;
}
a[k] = temp;
}
}
}
}
}
5.归并排序
归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。
- 把当前数组分化成n个单位为1的子数组,然后两两比较合并成单位为2的n/2个子数组;
- 继续进行这个过程,按照2的倍数进行子数组的比较合并,直到最终数组有序
def merge_sort(arr):
if len(arr) <= 1:
return arr
num = len(arr) // 2
left = merge_sort(arr[:num])
right = merge_sort(arr[num:])
return merge(left, right)
def merge(left, right):
L = 0
R = 0
result = []
while L < len(left) and R < len(right):
if left[L] < right[R]:
result.append(left[L])
L += 1
else:
result.append(right[R])
R += 1
result += left[L:]
result += right[R:]
return result
6.快速排序
快速排序也是利用分治法实现的一个排序算法。快速排序和归并排序不同,它不是一半一半的分子数组,而是选择一个基准数,把比这个数小的挪到左边,把比这个数大的移到右边。然后不断对左右两部分也执行相同步骤,直到整个数组有序。
- 用一个基准数将数组分成两个子数组
- 将大于基准数的移到右边,小于的移到左边
- 递归的对子数组重复执行1,2,直到整个数组有序
//left 为左起始位置, right 为右终点位置
void quickSort(int arr[], int left, int right)
{
int i, j, t, temp,
if (left > right)
return;
temp = arr[left]; // temp 保存的为基准数,可视为最左端为基准数
i = left;
j = right;
while(i < j)
{
//先从右边开始找
while(arr[j] >= temp && i < j)
j--;
// 再从左边找
while(arr[i] <= temp && i < j)
i++;
// 交换右左两数在数组中的位置
if(i < j)
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
//最终将基准数归位
a[left] = a[i];
a[i] = temp;
quickSort(arr, left, i-1); //继续处理左边的,递归方式
quickSort(arr, i+1, right); //继续处理右边的
}
7.堆排序
堆排序经常用于求一个数组中最大k个元素时。因为堆实际上是一个完全二叉树,所以用它可以用一维数组来表示。因为最大堆的第一位总为当前堆中最大值,所以每次将最大值移除后,调整堆即可获得下一个最大值,通过一遍一遍执行这个过程就可以得到前k大元素,或者使堆有序。
首先了解在一维数组中节点的下标:
- i节点的父节点下标 parent(i) = floor((i-1)/2)
- i节点的左子节点下标 left(i) = 2i + 1
- i节点的右子节点下标 right(i) = 2i + 2
算法步骤:
- 构造最大堆(Build Max Heap): 首先将当前元素放入最大堆下一个位置,然后将此元素依次和它的父节点比较,如果大于父节点就和父节点交换,直到比较到根节点。重复执行到最后一个元素。
- 最大堆调整(Max Heapify): 调整最大堆即将根节点移除后重新整理堆。整理方法为将根节点和最后一个节点交换,然后把堆看做n-1长度,将当前根节点逐步移动到其应该在的位置。
- 堆排序(HeapSort):重复执行2,直到所有根节点都已移除。
void max_heapify(int arr[], int start, int end)
{
int dad = start;
int son = dad * 2 +1; //表示左子节点
while(son <= end) //子节点指标在范围内才作比较
{
// 比较两个子节点的大小,选择最大的
if (son + 1 < = end && arr[son] < arr[son + 1] )
{son++;}
// 若父节点大于子节点代表调整完毕,直接跳出函数;否则交换父子内容,后再继续子节点和孙节点的比较
if(arr[dad] > arr[son])
{return;}
else{
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
//堆排序(先建立一个最大堆,然后将堆顶元素和最后一个元素交换 ,接着调整除最后一个元素的满二叉树为最大堆)
void heap_sort(int arr[], int len)
{
//初始化, i从最后一个父节点开始调整
for( int i = len/2 -1; i >= 0; i--)
{
max_heapify(arr, i, len-1);
}
//将堆顶元素和最后一个元素交换 ,接着调整除最后一个元素的满二叉树为最大堆,以此类推
for(int i = len - 1; i >= 0; i-- )
{
swap(arr[0], arr[i]);
max_heapify(arr, 0, i-1);
}
}
堆排序的整个过程如下图所示:
参考资料:
我理解的堆排序
经典排序算法总结与实现 --python
七种常见经典排序算法总结(C++实现)
十大经典排序算法