数据结构排序
- 1 知识框架
- 2 插入排序
- 2.1 直接插入排序
- 2.2 折半插入排序
- 2.3 希尔排序
- 3 交换排序
- 3.1 冒泡排序
- 3.2 快排
- 4 选择排序
- 4.1 简单选择排序
- 4.2 堆排序
- 5 归并和基数排序
- 5.1 归并排序
- 5.2 基数排序
1 知识框架
算法的稳定性:;两个相同的元素在排序算法完成后,两者的相对位置还没改变,则算法是稳定的。
内部排序算法总览:
2 插入排序
2.1 直接插入排序
// 升序排列L[]
void insertSort(int L[],int n)
{
for(int i = 2;i <= n;i ++)
{
if(L[i] < L[i - 1])
{
L[0] = L[i-1];
for(int j = i - 1;L[0] < L[j];j --)
{
L[j + 1] = L[j];
}
L[j + 1] = L[0];
}
}
} // L[0] 哨兵作用
空间效率:仅使用常数个辅助单元,因此为O(1)。
时间效率:最好的情况O(n),最坏的情况O(n²),因此时间复杂度为O(n²)
2.2 折半插入排序
直接插入是边比较边插入,折半先找到元素要插入的位置,然后统一移动元素
void insertSort(int L[],int n)
{
int i,j,low,high;
for(i = 2;i <= n;i ++)
{
L[0] = L[i];
low = 1,high = i - 1;
while(low <= high)
{
mid = (low + high) / 2;
if(L[mid] > L[0]) high = mid -1;
else low = mid + 1;
} // 找出待插入位置
for(j = i - 1;j >= high + 1;j --)
{
L[j + 1] = L[j];
}// 统一移动元素
L[high + 1] = L[0];
}
}
改变了元素的比较次数,移动次数并没有改变,时间复杂度仍为O(n²)。
但他适用于数据量不大的排序表。
2.3 希尔排序
void insertSort(int L[],int n)
{
int i,j,dk;
for(dk = n / 2;dk >= 1;dk = dk / 2) //dk的选取以及变化不是固定的
for(i = dk + 1;i <= n; i ++)
if(L[i] < L[i - dk])
{
L[0] = L[i];
for(j = j - dk;j > 0 && L[0] < L[j]; j-= dk)
{
L[j + dk] = L[j] //元素后移
}
L[j + dk] = L[0];
}
}
空间:O(1)
时间:涉及dk的选取,时间分析比较困难,约为O(n的1.3次方)
适用线性表为顺序存储的情况
3 交换排序
3.1 冒泡排序
void bubbleSort(int l[],int n)
{
int temp;
for(int i = 0;i <= n;i ++) //比较次数
for(int j = n - 1;j > i;j --)
{
if(l[j] < l[j - 1)
{
temp = l[j];
l[j] = l[j - 1];
l[j - 1] = temp;
}
}
}
空间:使用常数辅助,为O(1)
时间:平均时间复杂度O(n²)
3.2 快排
#include<stdio.h>
int n,i,j,a[100]; //定义全局变量
void quicksort(int left, int right)
{
int temp,t; //temp变量是基准,t变量是用来交换的第三变量
if(left > right) //跳出循环的第一个条件
{
return;
}
temp = a[left]; //此时选用的是第一个数当做基准
i = left; //i是1
j = right; //j是n
while(i != j) //当i,j没有走到一块的时候
{
while(a[j] >= temp && i<j) //j一步一步左移,查找比基准值小的数
{
j--;
}
while(a[i] <= temp && i<j) //i一步一步右移,查找比基准值大的数
{
i++;
}
if(i < j)
{
t = a[i]; //将j查到的比基准值的数与i查到的比基准值小的数交换
a[i] = a[j]; //即将小的数放在基准值左边
a[j] = t; //将大的数放在基准值右边
}
}
//将基准值归位
a[left] = a[i]; //当循环结束时,i=j,在中间某一位置
a[i] = temp; //将那一位置的数与基准值交换
//递归 ,将基准值左边分为一部分
//将基准值右边分为一部分
quicksort(left,i-1); //继续处理左半部分的数
quicksort(i+1,right); //继续处理右半部分的数
return ;
}
int main() //主函数
{
scanf("%d",&n); //用来限制输入的个数
for(i=1; i<=n; i++)
{
scanf("%d",&a[i]); //读入数据
}
quicksort(1,n); //调用快排函数
for(i=1; i<=n; i++)
{
printf("%d\t",a[i]); //循环输出
}
}
通过二叉树来判断 每次选取左边第一个数做根结点
空间:平均情况栈的深度为O(log₂n)
时间:平均复杂度为O(nlog₂n)
快排是所有内部排序算法中平均性能最优的排序算法
4 选择排序
4.1 简单选择排序
void selectSort(int l[],int n)
{
for(int i = 0;i < n - 1;i ++)
{
int min = i;
for(int j = i + 1;j < n;j ++)
if(l[j] < l[min])min = j;
if(min != i)swap(l[i],l[min]);
}
}
空间:O(1)
时间:O(n²)
4.2 堆排序
1.构造初始堆
架设构造大根堆
第一步从n / 2 - 1 ~ 1的顺序,看该结点的值是否小于子结点的值,若小于,则与子结点较大值交换。
第二步,若交换后的结点破坏了下一级的堆,子结点采用第一步的方法构造下一级堆。
2.输出堆顶元素
3.将堆底元素送入堆顶,重新构造堆
重新构造的堆的方法就是第二步
空间:O(1)
时间:平均复杂度为O(nlog₂n)
5 归并和基数排序
5.1 归并排序
将n个有序的子表,两两合并,直到合并成一个长度为n的有序表,称为2路归并排序
如何将两个子表合并:
假设两端有序表为A[l,m],A[m + 1,h]将两个表复制到B数组中,分别从两端取数进行比较,将较小的存入A中,一段会先存完,将没有存完的剩下那段的数字直接复制到A中
void merge(int a[],int l,int m,int h)
{
for(int i = l;i <= h;i ++)
b[i] = a[i];
for(i = l,j = mid + 1,k = i;i <= mid&& j <= h;k ++)
{
if(b[i] <= b[j]) // 较小放入a中
a[k] = b[i ++];
else
a[k] = b[j ++];
}
while(i <= mid) a[k ++] = b[i ++]; // 第一个表没复制完
while(j <= h) a[k ++] = b[j ++]; // 第二个表没复制完
}
空间复杂度:O(n)
时间:平均复杂度为O(nlog₂n)
5.2 基数排序
通常有两种方法:
1,最高位优先(MSD)
2,最低位优先(LSD)
举例低位优先
空间:使用r个队列,因此为O®
时间:进行d趟收集和分配,一趟分配需要O(n),一趟收集需要O®,因此时间复杂度为O(r + n)