排序
排序方法:插入、选择、交换、归并、分配排序
插入排序
直接插入排序
流程图:
代码实现:
void InsertSort(int arr[]){
for (int i = 1; i < arr.length; i++) {
//需要插入的数据
int temp = arr[i];
int j = i - 1;
//当插入的数据小于前面的数据时
while (j >= 0 && arr[j] > temp) {
//将插入的数据的前面的数据向后移动
arr[j + 1] = arr[j];
j--;
}
//插入数据
arr[++j] = temp;
}
}
特点:数据有序程度越高,越有效
适应场景:小规模数据或者基本有序时十分高效。
希尔排序
流程图:
代码实现:
void shellSort(int arr[]){
int len = arr.length;
//增量gap, 并逐步的缩小增量
for (int gap = len / 2; gap >= 1; gap /= 2) {
// 从第gap个元素,逐个对其所在的组进行直接插入排序
for (int i = gap; i < len; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
//移动
arr[j] = arr[j - gap];
j -= gap;
}
//当退出while后,就给temp找到插入的位置
arr[j] = temp;
}
}
}
交换排序
冒泡排序
代码实现:
void bubbleSort(int a[], int n) { //下面是函数bubbleSort的程序
//定义三个整型变量
int i,j,temp;
//用一个嵌套循环来遍历一遍每一对相邻元素 (所以冒泡函数慢嘛,时间复杂度高)
for (j=0;j<n-1;j++){
for (i=0;i<n-1-j;i++) {
if(a[i]>a[i+1]) { //从大到小排就把左边的">"改为"<" !!!
temp=a[i]; //a[i]与a[i+1](即a[i]后面那个) 交换
a[i]=a[i+1]; //基本的交换原理"c=a;a=b;b=c"
a[i+1]=temp;
}
}
}
}
快速排序(双向指针)
流程图:8
代码实现:
void quicksort(int arr[], int left, int right) {
if (left > right) return;
//基准
int temp = arr[left];
int i = left;
int j = right;
//顺序很重要,要先从右边开始查找
while (i < j) {
//从右向左移动找到第一个小于基准值
while (arr[j] >= temp && i < j) {
j--;
}
//从左向右移动找到第一个大于基准值
while (arr[i] <= temp && i < j) {
i++;
}
//满足条件则交换
if (i < j) {
int t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准i和j相等位置的数字交换
arr[left] = arr[i];
arr[i] = temp;
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
选择排序
直接选择排序
流程图:
代码实现:
// 选择排序:相邻两个元素进行比较,把大的元素的下标记录下来,一轮完整的比较之后,若最大值的下标不是len-i,则两个元素交换位置
void selectSort(int arr[],int len) {
int len = arr.length;
//i表示次数,共进行n-1次选择和交换
for (int i = 0; i < len; i++) {
//用min表示最小元素的下标
int min = i;
//找到当前排序期间的最小元素的下标
for (int j = i + 1; j < len; j++) {
//如果后面的元素小于当前最小元素,用min记录下来后面最小元素的下标
if (arr[j] < arr[min]) {
min = j; //保存位置
}
}
//如果min==i,说明min就是排序区间最小的不用交换
if (min != i) {
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
时间复杂度:O(\(n^2\)) 空间复杂度:O(1)
堆排序(大顶堆 、小顶堆 )
利用完全二叉树的结构来维护的一维数组
创建最大堆 :
堆排序(每次取最大值与叶子节点互换移除掉最大元素) :
代码实现:
public static void heapSort(int arr[]) {
int len = arr.length;
//创建堆
for (int i = (len - 1) / 2; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, len);
}
//调整堆结构+交换堆顶元素和末尾元素
for (int i = len - 1; i > 0; i--) {
//将堆顶元素与末尾元素进行交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//重新对堆进行调整
adjustHeap(arr, 0, i);
}
}
/**
* 调整堆
*
* @param arr
* @param parent
* @param length
*/
private static void adjustHeap(int arr[], int parent, int length) {
//将temp作为父节点
int temp = arr[parent];
//左孩子
int lChild = 2 * parent + 1;
while (lChild < length) {
//右孩子
int rChild = lChild + 1;
//如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (rChild < length && arr[lChild] < arr[rChild]) {
lChild++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (temp > arr[lChild]) {
break;
}
//把孩子及诶单的值赋值给父节点
arr[parent] = arr[lChild];
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
}
arr[parent] = temp;
}
归并排序
先分后归(先拆分为一个个有序的子序列,再将一个个有序子序列进行归并操作最后合并为一个有序的序列)
流程图:
复杂度
算法 | 最好情况 | 平均情况 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
分配排序
箱排序(桶排序)
流程图:
基数排序(分配和收集 )
(1)假设有欲排数据序列如下所示:
73 22 93 43 55 14 28 65 39 81
首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一一对应)中。
分配结果(逻辑想象)如下图所示:
分配结束后。接下来将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到如下仍然无序的数据序列:
81 22 73 93 43 14 55 65 28 39
接着,再进行一次分配,这次根据十位数值来分配(原理同上),分配结果(逻辑想象)如下图所示:
分配结束后。接下来再将所有桶中所盛的数据(原理同上)依次重新收集串接起来,得到如下的数据序列:
14 22 28 39 43 55 65 73 81 93
内部排序的方法比较
时间复杂度
(1)直接插入、直接选择、冒泡排序算法的时间复杂度为O(\(n^2\))
(2)快速、归并、堆排序算法的时间复杂度为O(\(nlog_{2}n\))
(3)希尔排序算法的时间复杂度很难计算:O(n\(log_{2}n\))或O(\(n^{1.25}\))
(4)基数排序算法的时间复杂度为O(d * (rd+n)),其中rd是基数,d是关键字的位数,n是元素个数。
稳定性
(1)直接插入、冒泡、归并和基数排序算法的稳定的。
(2)直接选择、希尔、快速和堆排序是不稳定的。
辅助空间
(1)直接插入、直接选择、冒泡、希尔和堆排序算法需要辅助空间为O(1).
(2)快速排序算法需要辅助空间为O(\(log_{2}n\))
(3)归并排序算法需要辅助空间为O(n)
(4)基数排序算法需要辅助空间为O(n+rd)