排序的分类
(1)内部排序法:指将需要处理的所有数据都i加载到内部存储器(内存)中进行排序。
(2)外部排序法:数据量过大,无法就全部加载到内存中,需要借助外部存储(文件等)进行排序。
算法的时间复杂度
度量一个程序(算法)执行时间的两种方法
(1)事后统计法
这种方法可行,但是有两个问题:一是想要对设计的算法的运行新能进行评测,需要实际运行该程序;二是所得时间的统计量依赖于计算机的硬件,软件等环境因素,这种方式,要是同一台计算机的相同状态下运行,才能比较那个栓发熟读更块。
(2)事前估算的方法
通过分析某个算法的时间复杂度来判断那个算法更优
常见的时间复杂度
1)常数阶O(1)
2)对数阶O(log2n)
3)线性阶O(n)
4)线性对数阶O(nlog2n)
5)平方阶O(n^2) 6)立方阶O(n^3)
7)K次方阶O(n^k) 8)指数阶O(2^n)
常见的算法时间复杂度由小到大依次为:O(1) < O(log2n) < O(n) < O(nlog2n) < O(n^2) < O(n^3) < O(n^k) < O(2^n)
平均时间复杂度和最坏时间复杂度
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
---|---|---|---|---|---|
冒泡 | O(n^2) | O(n^2) | 稳定 | O(1) | n小较好 |
交换 | O(n^2) | O(n^2) | 不稳定 | O(1) | n小较好 |
选择 | O(^2) | O(n^2) | 不稳定 | O(1)n小较好 | |
插入 | O(n^2) | O(n^2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) | B是真数(0-9),R是基数(个十百) |
Shell(希尔) | O(nlogn) | O(n^s)1 < s< 2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n^2) | 不稳定 | O(nlogn) | n大较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大较好 |
冒泡排序
冒泡排序(Bubble
Sorting)基本思想:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部。
算法步骤:
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
代码实现:
public static void main(String[] args) {
//创建一个大小为8的数组,斌且为其赋上初值
int[] arr = {1,8,2,5,7,4,3,9};
int length = arr.length;
//创建变量来减少不必要的循环,减小时间复杂度
boolean flag = false;
//从小到大进行排序
for(int i = 1;i < length; i++) {
flag = false;
for(int j = 0;j < length-i;j++) {
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
if(!flag) {
break;
}
}
}
选择排序
选择排序(select sorting)基本思想:第一次从arr[0] ~
arr[n-1]中选取最小值与arr[0]交换;第二次从arr[1] ~
arr[n-1]中挑选最小值与arr[1]交换;第三次从arr[2]~arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i] ~ arr[n-1];…,第n-1次从arr[n-1] ~
arr[n-1]中选出最小值与arr[n-2]交换,总共通过n-1次,得到一个按照从小到大排序的有序序列
算法步骤
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
代码 实现:
public static void main(String[] args) {
//创建一个大小为8的数组,斌且为其赋上初值
int[] arr = {1,8,2,5,7,4,3,9};
int length = arr.length;
for(int i = 0 ;i < length-1;i++) {
//每次查找之前将最小值的下标设置为i
int minFlag = i;
for(int j = i+1;j < length;j++) {
if(arr[j] < arr[minFlag]) {
//记录下每一轮循环的最小数值的下标
minFlag = j;
}
}
//一次大循环结束,将这轮的第一个数据和最小的数据位置进行交换
int temp = arr[i];
arr[i] = arr[minFlag];
arr[minFlag] = temp;
}
}
插入排序
插入排序(Insertion
Sorting)基本思想:把n个待排序的元素看成为一个有序和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排过程中每次从无序标中取出第一个元素,把它的排序码依次与有序表元素的排序码经行比较,将它插入到有序表中的适当位置,使之成为新的有序表
算法步骤
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
代码实现:
public static void main(String[] args) {
//创建一个大小为8的数组,斌且为其赋上初值
int[] arr = {1,8,2,5,7,4,3,9};
int length = arr.length;
for(int i = 1;i < length;i++) {
//记录下目前要进行比较的数据
int temp = arr[i];
//记录下目前数据的位置
int j = i;
//如果下标大于零,并且此下标的前一个元素大于要调换的元素,那么就把元素都往后移,因为之前的都一斤排好了,所以不用在考虑前面的顺序
while(j > 0 && temp > arr[j-1]) {
arr[j] = arr[j-1];
j--;
}
//如果最终找到的位置
if(j != i) {
arr[j] = temp;
}
}
}
希尔排序
希尔排序也是一种插入排序,它是简单擦汗如排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
希尔排序(Donald
Shell)基本思想:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键字越来越多,。当增量减至1时,整个文件恰被分成一组,便终止算法
算法步骤:
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
代码实现:
//【交换式】
public static void main(String[] args) {
//创建一个大小为8的数组,斌且为其赋上初值
int[] arr = {1,8,2,5,7,4,3,9};
int length = arr.length;
//gap为分组的个数,首次分组为数组总元素个数处于2,后面在继续在目前分组个数上除于2进行分组,一直到分组个数为1个的时候结束分组
for(int gap = length/2;gap > 0;gap /= 2) {
//从每次分组的第一个开始进行循环比较
for(int i = gap;i < length;i++) {
//依次在找到每一组的前几个在前置元素,进行比较插入
for(int j = i - gap;j >= 0;j -= gap) {
if(arr[j] > arr[j+gap]) {
int temp = arr[j];
arr[j] = arr[j+gap];
arr[j+gap] = temp;
}
}
}
}
}
//【移位式】
public static void main(String[] args) {
//创建一个大小为8的数组,斌且为其赋上初值
int[] arr = {1,8,2,5,7,4,3,9};
int length = arr.length;
//每次分组排依次序,一直到分组数量为1为止
for(int gap = length/2; gap > 0;gap/=2) {
//对不同分组进行插入排序
for(int i = gap;i < length;i++) {
int temp = arr[i];
int j = i;
while(j-gap >= 0 && temp < arr[j-gap]) {
arr[j] = arr[j-gap];
j -= gap;
}
if(j != i) {
arr[j] = temp;
}
}
}
快速排序
快速排序(QuickSort)是对冒泡排序的一种改进,基本思想通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行
算法步骤:
1.从数列中挑出一个元素,称为 “基准”(pivot);
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3URMeI31-1636711015598)(https://img-bog.csdnimg.cn/cba219d644ee4a808c0945c31f7d7a3e.gif#pic_center)]
代码实现:
public static void quickSort(int[] arr , int left ,int right) {
int r = right;//左下标
int l = left;//右下标
//中轴值
int pivot = arr[(r + l)/2];
//将数组里左边放的都是比pivot小的数,右边都是比pivot大的数
while(l < r) {
//从最左边开始找
while(arr[l] < pivot) {
l++;
}
//从最右边开始找
while(arr[r] > pivot) {
r--;
}
//如果没找到就退出
if(l >= r) {
break;
}
//如果没退出就说明找到了,就进行数据的交换
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//交换完后,发现arr[l] == pivot,则就要让r往前移一步
if(arr[l] == pivot) {
r--;
}
//交换完后,发现arr[r] == pivot,则就要让l往后移一步
if(arr[r] == pivot) {
l++;
}
//如果l == r,那么就要让l++,r--否则会出现栈溢出
if(l == r) {
l++;
r--;
}
//向左递归
if(left < r) {
quickSort(arr,left,r);
}
//向右递归
if(right > l) {
quickSort(arr,l,right);
}
}
归并排序
归并排序(Merge-Sort)是利用归并的思想进行的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分成一些小问题然后递归求解,而治的阶段将分的阶段得到的各答案结合在一起,即分而治之)
算法步骤:
1,申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2,设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3,比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4,重复步骤 3 直到某一指针达到序列尾;
5,将另一序列剩下的所有元素直接复制到合并序列尾。
代码实现:
public static void Mergesort(int[] arr,int left,int right,int[] temp) {
if(left < right) {
int mid = (left + right) / 2;
//分割左边的数组
Mergesort(arr, left, mid, temp);
//分割右边的数组
Mergesort(arr,mid+1,right,temp);
//合并
Merge(arr,left,mid,right,temp);
}
}
public static void Merge(int[] arr,int left,int mid,int right,int[] temp) {
int l = left;
int j = mid + 1;
int t = 0;
while(l <= mid && j <= right) {
//左边有序序列的元素比右边的有序序列的元素小,则拷贝左边的元素到temp数组,相反则拷贝右边的元素
if(arr[l] < arr[j]) {
temp[t++] = arr[l++];
}else {
temp[t++] = arr[j++];
}
}
//如果左边的元素没有拷贝完,则将其剩下元素进行拷贝
while(l <= mid) {
temp[t++] = arr[l++];
}
//如果右边的元素没有拷完,则将右边的剩下元素全部拷贝
while(j <= right) {
temp[t++] = arr[j++];
}
//将temp数组的元素拷贝到arr数组
t = 0;
int tempLeft = left;
while(tempLeft <= right) {
arr[tempLeft++] = temp[t++];
}
}
基数排序
基数排序基本思想:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零,然后,从最低位开始,依次进行依次排序,这样从最低位排序一直到最高位,排序完成以后,数列就变成了一个有序数列
基数排序说明:
(1)基数排序是对传统的桶排序的扩展,速度很快
(2)基数排序是经典的空间换时间的方式,占用内存空间很大,当对海量数据排序时,容易造成OutOfMemoryError。
(3)基数排序是稳定的。【注:假定在待排序的记录序列里,存在多个具有相同的关键字的记录,若金经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] =r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍然在r[j]之前,则这种排序算法称之为稳定的,否则称之为不稳定的】
(4)有负数的时候尽量不采用基数排序,若要采用则要进行改进
代码实现:
public static void main(String[] args) {
//创建一个大小为8的数组,斌且为其赋上初值
int[] arr = {190,82,25,512,754,4,143,9};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void radixSort(int[] arr) {
//找到待排数组里面位数最大的数
int max = arr[0];
for(int i = 1;i < arr.length;i++) {
if(arr[i] > max) {
max = arr[i];
}
}
//确定最大位数的数的位数
int len = (max+"").length();
//记录放入每个同里面的数据有那些
int[][] bucket = new int[10][arr.length];
//记录放入每个桶里的数据的个数
int[] buckNumber = new int[10];
//按照不同的位数来进行桶排序
for(int i = 0,n = 1;i < len;i++,n *= 10) {
//循环数组里面的数据,将其放入桶里
for (int j = 0; j < arr.length; j++) {
//取出每个数据每个位数上的数据
int temp = arr[j] / n % 10;
bucket[temp][buckNumber[temp]] = arr[j];
//当指定桶里多了一个数据,就在这个桶的数据总数上加一
buckNumber[temp]++;
}
//按照桶的顺序将桶里面的数据取出来
int temp = 0;
for (int k = 0; k < buckNumber.length; k++) {
if (buckNumber[k] != 0) {
for (int j = 0; j < buckNumber[k]; j++) {
arr[temp++] = bucket[k][j];
}
}
//每次循环结束后需要将存储每个同里元素个数的数组清零
buckNumber[k] = 0;
}
}
}