各排序算法时间复杂度和空间复杂度

img

冒泡排序

冒泡排序算法的原理如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现:

func maopao(arr []int)[]int{
   var tmp int
   for i:=0;i<len(arr)-1;i++{
      for j:=0;j<len(arr)-i-1;j++{
         if arr[j]>arr[j+1]{
            tmp=arr[j]
            arr[j]=arr[j+1]
            arr[j+1]=tmp
         }
      }
   }
   return arr
}
优化方式

1、优化外层循环,引入一个标签flag,在排序过程中若发生了交换则将flag置为1,各趟排序结束时检查flag,若未曾发生过交换说明已经有序,则终止算法,不再进行下一趟排序。代码如下:

func maopao(arr []int)[]int{
   var tmp int

   for i:=0;i<len(arr)-1;i++{

      falg:=0//每次遍历标志位都要先置为0,才能判断后面的元素是否发生了交换

      for j:=0;j<len(arr)-i-1;j++{
         if arr[j]>arr[j+1]{
            tmp=arr[j]
            arr[j]=arr[j+1]
            arr[j+1]=tmp
            falg=1
         }
      }
      if falg==0{
         break
      }
   }
   return arr
}

2、优化内层循环,在每趟扫描中,记住最后一次交换发生的位置lastExchange,(该位置之后的相邻记录均已有序),可以减少排序的趟数。

func maopao(arr []int)[]int{
   var tmp int
   k:=len(arr)-1
   var pos int
   for i:=0;i<len(arr)-1;i++{

      falg:=0//每次遍历标志位都要先置为0,才能判断后面的元素是否发生了交换

      for j:=0;j<k;j++{
         if arr[j]>arr[j+1]{
            tmp=arr[j]
            arr[j]=arr[j+1]
            arr[j+1]=tmp
            falg=1
            pos = j    //循环里最后一次交换的位置 j赋给pos
         }
      }
      if falg==0{
         break
      }
      k=pos  //将pos赋给k
   }
   return arr
}
快速排序

快速排序使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

挖坑填补法

首先,我们仍然是从end开始向前找一个小于28的数,找到后直接放在begin的位置,注意这里不是交换,如下图

在这里插入图片描述

箭头所指向的是我们选取的基准值,这时可以理解为它不在这个数列当中,而end对应的位置就出现了一个坑,这时,begin开始向后寻找,找到第一个大于基准值的数后放到end的位置,如下图

在这里插入图片描述

此时,begin的位置又会出现一个坑,这时再次让end向前移动继续找第一个小于基准值的数,放到begin的位置,再使begin向后移动找第一个大于基准值的数,重复此操作直到begin和end相遇,这时,把基准值放到该位置。这样,一趟快速排序结束,如下图

在这里插入图片描述

此时,28的前面都是小于它的数,其后面都是大于它的数,则28的位置确定,只需要递归进行快排后,就会变成一个有序数列

代码如下:

func main(){

   arr:=[]int{4,8,2,1,6,9,3,5,7,8,1,4}
   arr=quick(arr,0,len(arr)-1)
   fmt.Println(arr)
}

选第一个数为基准数

func quick(arr []int,low,high int)[]int{
   if low>high{
      return arr
   }
   i:=low
   j:=high
   t:=arr[i]
   for i<j{
      for i<j&&arr[j]>=t{
         j--
      }
      arr[i]=arr[j]
      for i<j&&arr[i]<=t{
         i++
      }
      arr[j]=arr[i]
   }
   arr[i]=t
   quick(arr,low,i-1)
   quick(arr,i+1,high)

   return arr
}

选择最后一个数为基准数

func quick(arr []int,start int,end int)[]int{

   if end<start{
      return arr
   }


   i,j:=start,end
   t:=arr[j]        //这里将最后一个元素设为基准数

   for j>i{

      for j>i&&arr[i]<=t{       //要先找前面的,再找后面的
         i++
      }
      arr[j]=arr[i]

      for j>i&&arr[j]>=t{
         j--
      }
      arr[i]=arr[j]


   }
   arr[j]=t

   quick(arr,start,i-1)
   quick(arr,i+1,end)
   return arr
}
选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

插入排序

基本思想是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。

img

func main(){

	arr:=[]int{4,8,2,1,6,9,3,5,7,8,1,4}
	arr=charu(arr)
	fmt.Println(arr)
}

func charu(arr []int)[]int{

	if len(arr)<2{
		return arr
	}
	for i:=1;i<len(arr);i++{

		for index:=i;(index>0)&&(arr[index]<arr[index-1]);index--{	
    	//这里index>0 必须在前面,
        //如果写成(arr[index]<arr[index-1])&&index>0会报错
        
		arr[index],arr[index-1]=arr[index-1],arr[index]
            
		}
	}

	return arr
}
希尔排序 归并排序
func main(){
	arr:=[]int{9,5,7,4,8,6,3,2,1}
	n:=len(arr)-1
	mergesort(arr,0,n)
	fmt.Println(arr)
}

func mergesort(arr []int,start,end int){	//分割,直到分成每堆只有2个元素
	if end <= start{
		return
	}
	mid:=(start+end)/2
	mergesort(arr,start,mid)
	mergesort(arr,mid+1,end)
	paixu(arr,start,mid,end)
}

func paixu(arr []int,start,mid,end int){	//把两个有序数组合并成一个
	temp:=[]int{}
	left :=	start
	right := mid+1
	for left<=mid && right<=end{
		if arr[left]<=arr[right]{
			temp=append(temp,arr[left])
			left++
		}else{
			temp=append(temp,arr[right])
			right++
		}
	}
	for left<=mid{
		temp=append(temp,arr[left])
		left++
	}
	for right<=end{
		temp=append(temp,arr[right])
		right++
	}

	for k,v:=range temp{
		arr[start+k]=v
	}
}
堆排序

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

package main

import "fmt"

func main(){

	arr:=[]int{4,8,2,1,6,9,3,5,7,8,1,4}
	dui(arr)
	fmt.Println(arr)
}

func swap(arr []int,a,b int){
	arr[a],arr[b]=arr[b],arr[a]
}

func heapadjust(arr []int,i,m int){

	son:=i*2+1
	for son<m{
		if son+1<m&&arr[son]<arr[son+1]{//首先判断选出左节点大还是右节点大
			son++
		}
		if arr[i]>arr[son]{
			break
		}else{
			swap(arr,i,son)
			i=son
			son=i*2+1
		}
	}
}

func dui(arr []int){
	for i:=len(arr)/2-1;i>=0;i--{	//这里是先变成大顶堆
		heapadjust(arr,i,len(arr))
	}
	
	for i:=len(arr)-1;i > 0;i--{	
    //将最大值与最后一个交换,然后对前i-1重新排序
	swap(arr,0,i)
	heapadjust(arr,0,i-1)

	}
}
归并排序 和 快速排序比较

都是分治法的应用,将原问题分解成一系列子问题。

快速排序是自顶向下,分解然后排序,因为就地排序不用合并。

归并排序是自底向上,先分解成不能再分然后排序将两个已排序的合并。

归并排序更稳定,因为相同的元素的位置不会被改变。快速排序不稳定是因为元素交换是跨元素的,相邻相同元素可能发生位置交换

快排稳定版

稳定 指:数组中具有相同元素,排序过后这些元素相对次序保持不变,则稳定

思路

新创建一个辅助数组,原数组只保存不需要交换的元素,将要交换的元素放到辅助数组中,最后再合并起来避免交换。