冒泡排序
算法思想:
每次比较相邻元素,若逆序则交换位置,每一趟比较n-1次,确定一个最大值。故需比较n趟,来确定n个数的位置。
- 外循环来表示比较的趟数,每一趟确定一个最大数的位置
- 内循环来表示相邻数字两两比较。
private static void bubble_sort(int[] nums) {
for (int i = 0; i < nums.length; i++){
for (int j = 0; j < nums.length-1; j++){
if (nums[j]>nums[j+1]) {
int temp=nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
改进:
记录某次遍历时最后发生数据交换的位置pos,这个位置之后的数据显然已经有序了。因此通过记录最后发生数据交换的位置就可以确定下次循环的范围了。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
private static void bubble_sort2(int[] nums) {
for (int i = 0; i < nums.length; i++){
int flag=nums.length-1;
for (int j = 0; j < flag; j++){//扫描的位置
if (nums[j]>nums[j+1]) {
int temp=nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
flag=j;//交换的位置
}
}
}
}
- 平均时间复杂度:O(N^2)
- 最佳时间复杂度:O(N)
- 最差时间复杂度:O(N^2)
- 空间复杂度:O(1)
插入排序
插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间(去存待插入的元素)的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
private static void InsertSort(int[] nums) {
for (int i = 1; i <nums.length; i++) {//从第二个元素开始插入
int temp = nums[i];
int j;
for (j=i-1; j >= 0; j--) {//已排好序的是待插入元素之前的,从后往前依次比较
if (temp<nums[j]) nums[j+1]=nums[j];//往后移一位
else break;//找到比待插入元素小的,此时将待插入元素插入此元素的后面
}
nums[j+1]=temp;//将待插入元素插入此元素的后面
}
}
改进思路: 在往前找合适的插入位置时采用二分查找的方式,即折半插入。
private static void InsertSort2(int[] nums) {
for (int i = 1; i <nums.length; i++) {//从第二个元素开始插入
int temp = nums[i];
int left = 0;
int right = i-1;
while (left<=right)//找比待插入元素小的元素的位置left
{
int middle = (left+right)/2;
if (nums[middle]>temp)
right = middle-1;
else
left = middle+1;
}
int j;
for (j=i-1; j >= left; j--) {//已排好序的是待插入元素之前的,从后往前依次比较
nums[j+1]=nums[j];//往后移一位
}
nums[j+1]=temp;//将待插入元素插入此元素的后面
}
}
- 平均时间复杂度:O(N^2)
- 最差时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 排序方式:In-place
- 稳定性:稳定
希尔排序
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。
对相隔某个增量所构成的数组进行插入排序。
private static void Shellsort(int[] nums) {
for (int gap=nums.length/2; gap>0; gap/=2) {//定义间隔
//插入排序
for (int i=gap;i<nums.length;i++) {
int temp=nums[i];
int j;
for (j=i-gap;j>=0;j-=gap) {
if (temp<nums[j]) nums[j+gap]=nums[j];
else break;
}
nums[j+gap]=temp;
}
}
}
- 平均时间复杂度:O(Nlog2N)
- 最佳时间复杂度:
- 最差时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
- 复杂性:较复杂
选择排序
首先在序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
private static void selectSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int j=i;
int min=j;//min用来记录最小值的坐标
for (; j < nums.length; j++) {
if (nums[j] < nums[min]) min=j;
}
if(nums[min]<nums[i-1]){
int temp=nums[i-1];
nums[i-1]=nums[min];
nums[min]=temp;
}
}
}
- 平均时间复杂度:O(N^2)
- 最佳时间复杂度:O(N^2)
- 最差时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 排序方式:In-place
- 稳定性:不稳定
改进方法:上述方法每次只确定一个最小值,可采取每一次比较找出最大值与最小值,同时确定两个值。
快速排序
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
特别注意:若以第一个元素为基准数,在哨兵互走过程需右边的哨兵先走。 原因很好理解,哨兵互走交换的过程就是不断排序的过程。若右边的哨兵先走,不管走多少次,最后相遇时的那个数是小于基准数的。这时与基准数交换,正好分为两个序列。可若是左边的先走,相遇在大于基准数上就不好办了。
private static void quickSort(int[] nums,int start,int end) {
if (start>=end) return;
int pivot = nums[start];
int i=start;
int j=end;
while(i < j){
while (nums[j]>pivot&&i<j) j--;//右哨兵先走
while (nums[i]<=pivot&&i<j) i++;
//以上两个循环找到左侧比pivot大的与右侧比pivot小的
swap(nums,i,j);
}//此循环将比pivot大的分一组,小的分一组
//最终位置,最后一个小于pivot的位置是j指向的
swap(nums,start,j);//将基准值插入中间,即此时j指向pivot的位置
quickSort(nums,start,j-1);
quickSort(nums,j+1,end);
}
- 平均时间复杂度:O(NlogN)
- 最佳时间复杂度:O(NlogN)
- 最差时间复杂度:O(N^2)
- 空间复杂度:根据实现方式的不同而不同
归并排序
首先把一个未排序的序列从中间分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一个一个的有序的数据,再把这些有序的数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列。
private static int[] Mergesort(int[] nums) {
if (nums.length<=1) return nums;
int mid= (nums.length-1)/2;
//int []left=Arrays.copyOfRange(nums,0,mid+1);
//int []right=Arrays.copyOfRange(nums,mid+1,nums.length);
int []sortleft =Mergesort(Arrays.copyOfRange(nums,0,mid+1));
int []sortright=Mergesort(Arrays.copyOfRange(nums,mid+1,nums.length));
return merge(sortleft,sortright,nums);
}
private static int[] merge(int[] sortleft, int[] sortright, int[] nums) {
int l=0;
int r=0;
int index=0;
while (l<sortleft.length && r<sortright.length){
if (sortleft[l]<sortright[r]){
nums[index]=sortleft[l];
l++;
index++;
}else {
nums[index]=sortright[r];
r++;
index++;
}
}
while (l<sortleft.length) nums[index++]=sortleft[l++];
while (r<sortright.length) nums[index++]=sortright[r++];
return nums;
}
private static int[] mergerSort(int[] nums) {//形成有序数列
if (nums.length <= 1) return nums;
int mid= (nums.length-1)/2;
//划分为两部分
int []left = Arrays.copyOfRange(nums,0,mid+1);
int []right = Arrays.copyOfRange(nums,mid+1,nums.length);
//将左右变为有序数列
int []sortleft = mergerSort(left);
int []sortright = mergerSort(right);
//归并
int m = 0, i = 0, j = 0;
while (i < sortleft.length && j < sortright.length) {
nums[m++] = sortleft[i] < sortright[j] ? sortleft[i++] : sortright[j++];
}
while (i < sortleft.length)
nums[m++] = sortleft[i++];
while (j < sortright.length)
nums[m++] = sortright[j++];
return nums;
}
- 平均时间复杂度:O(nlogn)
- 最佳时间复杂度:O(n)
- 最差时间复杂度:O(nlogn)
- 空间复杂度:O(n)
- 排序方式:In-place
- 稳定性:稳定
计数排序
计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。
private static int[] countSort(int[] nums) {
int min = nums[0];
int max = nums[0];
for (int i = 1; i< nums.length;i++){
if (nums[i] < min) min = nums[i];
if (nums[i] > max) max = nums[i];
}
int [] count = new int[max-min+1];
for (int i=0;i<nums.length;i++){
count[nums[i]-min]++;//count[i]表示nums中i出现的次数。
}
int []newnum =new int[nums.length];
int j=0;
for (int i=0;i<count.length;i++){
while (count[i]>0){
newnum[j++] = i+min;
count[i]--;
}
}
return newnum;
}
桶排序
桶排序假设待排序的一组数均匀独立的分布在一个范围中,并将这一范围划分成几个子范围(每个桶的范围成顺序)。然后基于某种映射函数f ,将待排序列的元素映射到第i个桶中。
映射函数一般是 f = array[i] / k; k^2 = n; n是所有元素个数
private static void bucketSort(int[] nums) {
int n = nums.length;
if (n <= 0)
return;
ArrayList<Integer>[] bucket = new ArrayList[n];
// 创建空桶
for (int i = 0; i < n; i++)
bucket[i] = new ArrayList<Integer>();
// 根据规则将序列中元素分散到桶中,每个桶之间的大小区间有顺序
for (int i = 0; i < n; i++) {
int bucketIndex = (int) (nums[i] / Math.sqrt(n));//映射函数
bucket[bucketIndex].add(nums[i]);
}
// 对各个桶内的元素进行排序
for (int i = 0; i < n; i++) {
Collections.sort((bucket[i]));
}
// 合并所有桶内的元素
int index = 0;
for (int i = 0; i < n; i++) {
for (int j = 0, size = bucket[i].size(); j < size; j++) {
nums[index++] = bucket[i].get(j);
}
}
}
- 平均时间复杂度:O(n + k)
- 最佳时间复杂度:O(n + k)
- 最差时间复杂度:O(n ^ 2)
- 空间复杂度:O(n * k)
- 稳定性:稳定
基数排序
原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。
private static void RadixSort(int[] nums) {
int maxlen=maxIndex(nums);
int radix=1;
for (int i=0; i<maxlen; i++){//位数等于排序次数
//桶排序
ArrayList<Integer> []lists= new ArrayList[10];
for (int j=0; j< 10; j++) lists[j]=new ArrayList<>();
for (int j=0;j< nums.length;j++){
lists[(nums[j]/radix)%10].add(nums[j]);
}
for (int j=0;j<10;j++) Collections.sort(lists[j]);
int index = 0;
for (int k = 0; k < nums.length; k++) {
for (int j = 0, size = lists[k].size(); j < size; j++) {
nums[index++] = lists[k].get(j);
}
}
//依次取个位、十位、、、、
radix*=10;
}
}
- 时间复杂度:O(k*N)
- 空间复杂度:O(k + N)
- 稳定性:稳定
堆排序
堆是一种特殊的树。只要满足以下两点,它就是一个堆:
- 堆是一个完全二叉树;
- 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。
堆的构造
一般用数组(层序排列)来表示堆,用数组的1号下标存储根节点。那么对于一般的节点 i,其左孩子为 i*2,右孩子为 i*2+1,其父亲节点则为 i/2。(当然所有派生出来的节点x都要先判断 0<x<=n,表示存在
堆的插入(以大根堆为例)
插入元素的位置,一定是n的下一个位置。
再将形成的堆进行堆化调整(自底向上),是每个节点均符合大根堆的性质。
删除堆顶元素
用最后一个结点覆盖堆顶节点,然后堆调整。
堆排序
基本思想:不断取出堆顶元素,进行堆调整
public void sort(){
/*
* 第一步:将数组堆化
* beginIndex = 第一个非叶子节点。
* 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
* 叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
*/
int len = arr.length - 1;
int beginIndex = (len - 1) >> 1;
for(int i = beginIndex; i >= 0; i--){
maxHeapify(i, len);
}
/*
* 第二步:对堆化数据排序
* 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
* 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
* 直至未排序的堆长度为 0。
*/
for(int i = len; i > 0; i--){
swap(0, i);
maxHeapify(0, i - 1);
}
}
private void maxHeapify(int index,int len){
int li = (index << 1) + 1; // 左子节点索引
int ri = li + 1; // 右子节点索引
int cMax = li; // 子节点值最大索引,默认左子节点。
if(li > len) return; // 左子节点索引超出计算范围,直接返回。
if(ri <= len && arr[ri] > arr[li]) // 先判断左右子节点,哪个较大。
cMax = ri;
if(arr[cMax] > arr[index]){
swap(cMax, index); // 如果父节点被子节点调换,
maxHeapify(cMax, len); // 则需要继续判断换下后的父节点是否符合堆的特性。
}
}