github.com/qiuhoude/go-web@v0.0.0-20220223060959-ab545e78f20d/algorithm/datastructures/sort_/sort_test.go (about) 1 package sort_ 2 3 import ( 4 "fmt" 5 "testing" 6 ) 7 8 /* 9 冒泡排序 10 只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足 11 大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置, 12 重复 n 次,就完成了 n 个数据的排序工作 13 属于稳定性算法(相同值排序前后顺序不变) 14 空间 O(1) 15 最好 O(n) 16 最坏 O(n^2) 17 18 满有序度 = n*(n-1)/2 19 逆序度 = 满有序度 - 初始有序度 20 逆序度 = 就是需要要换的次数 21 平均逆序度 = (满有序度 - 0)/ 2 = n*(n-1)/4 22 平均复杂度 约等于 O(n*(n-1)/4) , n值很大时 O(n^2) 23 24 */ 25 func bubbleSort(arr []int) { 26 n := len(arr) 27 if n <= 0 { 28 return 29 } 30 for i := 0; i < n; i++ { 31 flag := false // 提前退出标志 32 for j := 0; j < n-1; j++ { 33 if arr[j] > arr[j+1] { 34 arr[j], arr[j+1] = arr[j+1], arr[j] 35 flag = true // 有交换 36 } 37 } 38 if !flag { 39 break 40 } 41 } 42 } 43 44 func TestBubbleSort(t *testing.T) { 45 arr := []int{4, 5, 6, 3, 2, 1} 46 bubbleSort(arr) 47 fmt.Println(arr) 48 } 49 50 /* 51 插入排序 52 53 (后面排序算法思路可以借鉴) 54 将数组中的数据分为两个区间,已排序区间和未排序区间,初始已排序区间只有一个 55 元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中 56 找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区 57 间中元素为空,算法结束 58 59 是稳定性排序 60 空间 O(1) 61 最好 O(n) 62 最坏 O(n^2) 63 平局 O(n^2) 64 */ 65 func insertionSort(arr []int) { 66 n := len(arr) 67 if n < 1 { 68 return 69 } 70 for i := 1; i < n; i++ { 71 v := arr[i] // 记录要插入的值 72 j := i - 1 // 0~j 已排序区间 73 // 查找要插入的位置 74 for ; j >= 0; j-- { 75 if arr[j] > v { 76 arr[j+1] = arr[j] // 移动数据 77 } else { 78 break 79 } 80 } 81 arr[j+1] = v // 插入数据 82 } 83 } 84 85 func TestInsertionSort(t *testing.T) { 86 arr := []int{4, 5, 6, 3, 2, 1} 87 insertionSort(arr) 88 fmt.Println(arr) 89 } 90 91 /* 92 选择排序 93 94 选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次 95 会从未排序区间中找到最小的元素,将其放到已排序区间的末尾 96 97 非稳定性的排序 , 例如 [5,8,5,2,9] ,后面的的5就会和前面的5进行换位置 98 空间 O(1) 99 最好 O(n) 100 最坏 O(n^2) 101 平局 O(n^2) 102 */ 103 104 func selectionSort(arr []int) { 105 n := len(arr) 106 if n < 1 { 107 return 108 } 109 for i := 0; i < n-1; i++ { 110 minIndex := i // 最小值的位置 111 for j := i; j < n; j++ { // 寻找最小值位置 112 if arr[j] < arr[minIndex] { 113 minIndex = j 114 } 115 } 116 // 交换位置 117 arr[i], arr[minIndex] = arr[minIndex], arr[i] 118 } 119 } 120 121 func TestSelectionSort(t *testing.T) { 122 arr := []int{4, 5, 6, 3, 2, 1, 8, 7} 123 selectionSort(arr) 124 fmt.Println(arr) 125 } 126 127 /* 128 归并排序 129 130 把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了 131 132 递推公式: mergeSort(arr[p:r]) = mergeSort(arr[p:mid]) + mergeSort(arr[mid+1:r]) 133 merge() 进行合并数组操作 134 mid = (p + r) / 2 135 终止条件 p>=r 136 137 递归代码的时间复杂度也可以写成递推公式 T(a) = T(b) + T(c) + K 138 139 T(1) = C; n=1 时,只需要常量级的执行时间,所以表示为 C。 140 T(n) = 2*T(n/2) + n; n>1 141 142 T(n) = 2*T(n/2) + n 143 = 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n 144 = 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n 145 = 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n 146 ...... 147 = 2^k * T(n/2^k) + k * n 148 = 2^(log2(n)) * C+ log(n)*n 149 = 2*n * C + log(n)*n 150 = nlog(n) + 2n*C 151 152 当 T(n/2^k) = T(1) = C 153 n/2^k = 1 => k = log2(n) 154 155 所以任何情况下时间复杂度 O(nlog(n)) 156 157 空间复杂度 158 2^x=n => x=log2(n) 159 看上去要去要分配log2(n)次,每次需要分配n/2^k个空间, O(n * log2(n)); 160 但是同一时刻只最多只有n个空间被分配 161 所以复杂度是 O(n) 162 */ 163 func mergeSortEnter(arr []int) { 164 n := len(arr) 165 if n <= 1 { 166 return 167 } 168 mergeSort(arr, 0, n-1) 169 } 170 171 func mergeSort(arr []int, p, r int) { 172 if p >= r { 173 return 174 } 175 mid := p + ((r - p) >> 1) 176 // 分而治之 177 mergeSort(arr, p, mid) 178 mergeSort(arr, mid+1, r) 179 // 将 A[p...q] 和 A[q+1...r] 合并为 A[p...r] 180 merge(arr, p, mid, r) // 归并 181 } 182 183 func merge(arr []int, p, mid, r int) { 184 tmp := make([]int, r-p+1) 185 i := p // 前数组的指针 186 j := mid + 1 // 后数组指针 187 k := 0 188 for ; i <= mid && j <= r; k++ { 189 if arr[i] <= arr[j] { // 前数组 等于 后数组, 取前数组的值,保证稳定性 190 tmp[k] = arr[i] 191 i++ 192 } else { 193 tmp[k] = arr[j] 194 j++ 195 } 196 } 197 // 剩余的数据,填充到数组中 198 for ; i <= mid; k++ { 199 tmp[k] = arr[i] 200 i++ 201 } 202 for ; j <= r; k++ { 203 tmp[k] = arr[j] 204 j++ 205 } 206 copy(arr[p:r+1], tmp) 207 } 208 209 func TestMergeSort(t *testing.T) { 210 arr := []int{4, 5, 6, 3, 2, 1, 8, 7} 211 mergeSortEnter(arr) 212 t.Log(arr) 213 } 214 215 /* 216 快速排序 217 218 如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间 219 的任意一个数据作为 pivot(分区点) 220 遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放 221 到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间 222 都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的 223 224 递推公式: quickSort(arr,p,r) = quickSort(arr,p,q-1) + quickSort(arr,q+1,r) 225 递归出口 p>=r 226 227 空间复杂度 O(1) 228 最坏 O(n^2) 选取的 pivot的位置点是最大值或最小值 229 平均 O(nlogn) 230 231 利用的分治 和 分区的思想 232 233 */ 234 235 func quickSortEnter(arr []int) { 236 n := len(arr) 237 if n <= 1 { 238 return 239 } 240 quickSort(arr, 0, n-1) 241 } 242 243 func quickSort(arr []int, p, r int) { 244 if p >= r { 245 return 246 } 247 q := partition(arr, p, r) // 获取区服点 248 quickSort(arr, p, q-1) 249 quickSort(arr, q+1, r) 250 } 251 252 /* 253 进行分区 254 思路: 也可以根据插入排序或选择排序的分区思路,把原区域分为 255 [0:i] 比pivot小的值, [i+1:end] 比pivot大的值两个区域 256 步骤: 1. 找arr[end] 为 pivot值 257 2. 开始遍历每个元素,只要 <pivot 就放到左边区域的尾部进行替换,左边区域尾部的下标+1 258 3. 最后 end 与 左边区域尾部下标值进行交换, 259 4. 左边区域尾部下标值就是要找的位置 260 261 262 选取pivot的优化思路: 263 1. 三数取中法 从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点 264 2. 随机法 265 */ 266 func partition(arr []int, p, r int) int { 267 // 取 r位置为 pivot点 268 pivot := arr[r] 269 i := p // p ~ i 是<pivot 270 for j := p; j < r; j++ { 271 if arr[j] < pivot { 272 if j != i { 273 arr[j], arr[i] = arr[i], arr[j] //放到最后面 274 } 275 i++ 276 } 277 } 278 arr[i], arr[r] = arr[r], arr[i] 279 return i 280 } 281 282 func TestQuickSort(t *testing.T) { 283 arr := []int{4, 5, 6, 3, 2, 1, 8, 7} 284 quickSortEnter(arr) 285 t.Log(arr) 286 287 } 288 289 // 如何在 O(n) 的时间复杂度内查找一个无序数组中的第 K 大元素 290 func findMaxK(arr []int, k int) int { 291 n := len(arr) 292 if n < 1 || n < k { 293 return -1 294 } 295 k = n - k + 1 // 因为 partition 是左小右大的正序, 找第K大 等于 找第 n-k+1 小的元素 296 r := n - 1 297 p := partition(arr, 0, r) 298 for j := 0; j < n; j++ { // 最多查找 n 次,防止死循环 299 if p+1 == k { 300 return p 301 } else if k > p+1 { // 继续右边找 302 p = partition(arr, p+1, r) 303 } else { // 继续左边找 304 p = partition(arr, 0, p-1) 305 } 306 } 307 return -1 308 } 309 310 func TestFindMaxK(t *testing.T) { 311 arr := []int{4, 5, 6, 3, 2, 1, 8, 7, 1, 1} 312 k := 1 313 i := findMaxK(arr, k) 314 if i != -1 { 315 t.Log(arr[i]) 316 } 317 } 318 319 /* 320 分库分页 321 322 业务折衷法-禁止跳页查询 323 (1)用正常的方法取得第一页数据,并得到第一页记录的time_max 324 (2)每次翻页,将order by time offset X limit Y,改写成order by time where time>$time_max limit Y 325 以保证每次只返回一页数据,性能为常量。 326 327 业务折衷法-允许模糊数据 328 (1)将order by time offset X limit Y,改写成order by time offset X/N limit Y/N 329 330 二次查询法 331 (1)将order by time offset X limit Y,改写成order by time offset X/N limit Y (N是分库的数量) 332 (2)找到最小值time_min 333 (3)between二次查询,order by time between $time_min and $time_i_max 334 (4)设置虚拟time_min,找到time_min在各个分库的offset,从而得到time_min在全局的offset 335 (5)得到了time_min在全局的offset,自然得到了全局的offset X limit Y 336 */