# 写在开头的话

趁着疫情隔离期间,打算把以前丢掉的算法重新捡起来。。。ε=(´ο`*))) 唉

第一天,先复习以前的知识:

# 各种排序

# 插入排序

无非就是从后往前去遍历,如果满足条件就把值 place:

func (A SortByAgeName) InsertSort() {
	for i := 1; i < A.Len(); i++ {
		s := A[i]
		j := i - 1 // 游标
		for j >= 0 && A[j].Age > s.Age {
			A[j+1] = A[j]
			j--
		}
		A[j+1] = s
	}
}

这个排序显然是 stable 的。

# 折半插入排序

插入排序有一个缺点那就是游标是 stepbystep,折半插入就是就是折半查找加上插入排序,它的复杂度并没有改变。

这里参考了 https://zhuanlan.zhihu.com/p/139579615 的代码

普通二分搜索:

let binatySearch = function(nums){
  let left = 0, right = nums.length - 1
  
  while (left <= right) {
    let mid = (left + right) >> 1
    if (nums[mid].value == target) {
      return mid
    }
    else if (nums[mid].value < target) {
      left = mid + 1
    }
    else { // nums[mid].value > target
      right = mid - 1
    }
  }
  return -1 
}

右边界二分搜索:

left, right := 0, i-1
for left <= right {
    mid := left + (right-left)/2
    if A[mid].Age <= s.Age { 
    	left = mid + 1
    } else {
    	right = mid - 1
    }
}

最终 right 停的地方就是右边界第一个满足或不满足条件的值。

左边界二分搜索:

left, right := 0, i-1
for left <= right {
    mid := left + (right-left)/2
    if A[mid].Age < s.Age { 
    	left = mid + 1
    } else {
    	right = mid - 1
    }
}

最终 left 停的地方就是右边界第一个满足或不满足条件的值。

如果忘记的话就是查看上面的链接,这个东西容易忘,而且也挺难去理解的。

二分插入排序就很好写了:

func (A SortByAgeName) HalfInsertSort() {
	for i := 1; i < A.Len(); i++ {
		s := A[i]
		left, right := 0, i-1
		for left <= right {
			mid := left + (right-left)/2
			if A[mid].Age <= s.Age { // 因为要保证稳定,Stable
				left = mid + 1
			} else {
				right = mid - 1
			}
		}
		for j := i - 1; j >= right+1; j-- {
			A[j+1] = A[j]
		}
		A[right+1] = s
	}
}

# 希尔排序

func (A SortByAgeName) ShellInsertSort() {
	step := A.Len() / 2
	for ; step >= 1; step /= 2 {
		for j := step; j < A.Len(); j += step {
			s := A[j]
			k := j - step
			for k >= 0 && A[k].Age > s.Age {
				A[k+step] = A[k]
				k -= step
			}
			A[k+step] = s
		}
	}
}

就是多个步长使用插入排序,这个东西我至少没用过。

# 冒泡排序

func (A SortByAgeName) BubbleSort() {
	for i := 1; i < A.Len(); i++ {
		for j := 0; j < A.Len()-i; j++ {
			if A[j+1].Age < A[j].Age {
				A[j], A[j+1] = A[j+1], A[j]
			}
		}
	}
}

冒泡排序属于交换排序,每次冒泡就把最大的那个元素交换到最末尾。

# 快排

func (A SortByAgeName) QuickSort(left, right int) {
	i, j := left, right
	if right <= left {
		return
	}
	t := rand.Intn(right-left+1) + left
	base := A[t]
	A[right], A[t] = A[t], A[right]
	for left < right {
		for left < right && A[left].Age <= base.Age {
			left += 1
		}
		A[right] = A[left]
		for left < right && A[right].Age >= base.Age {
			right -= 1
		}
		A[left] = A[right]
	}
	// 永远会多保存一份副本,所以直接把 base 填在 right 处就行了
	A[right] = base
	A.QuickSort(i, right-1)
	A.QuickSort(right+1, j)
}

这个快排版本和我之前写的有些不一样的地方在于,选取 base 是随机的而不是直接选取 right 或 left 作为 base。

代码并不复杂,忘记的话就多看几遍就懂了。

# 堆排序

理解堆排序的核心在于理解建堆,先上代码:

func (A SortByAgeName) HeapSort() {
	adjust := func(start, len int) {
		i := start
		j := start*2 + 1
		for j < len {
			if j+1 < len && A[j].Age < A[j+1].Age { // 有左孩子,并且左孩子比右孩子小
				j = j + 1
			}
			if A[i].Age < A[j].Age {
				A[j], A[i] = A[i], A[j]
			}
			i = j
			j = i*2 + 1
		}
	}
	// create
	for i := A.Len() / 2; i >= 0; i-- {
		adjust(i, A.Len())
	}
	// sort
	for i := 0; i < A.Len(); i++ {
		A[0], A[A.Len()-i-1] = A[A.Len()-i-1], A[0]
		adjust(0, A.Len()-i-1)
	}
}

每次在于自上而下进行调整。

更新于

请我喝[茶]~( ̄▽ ̄)~*

Kalice 微信支付

微信支付

Kalice 支付宝

支付宝