手撸golang 基本数据结构与算法 插入排序

Posted ioly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撸golang 基本数据结构与算法 插入排序相关的知识,希望对你有一定的参考价值。

缘起

最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)
本系列笔记拟采用golang练习之

插入排序

插入排序是一种从序列左端开始依次对数据进行排序的算法。

在排序过程中,左侧的数据陆续归位,
而右侧留下的就是还未被排序的数据。

插入排序的思路就是从右侧的未排序区域内取出一个数据,
然后将它插入到已排序区域内合适的位置上。

时间复杂度和冒泡排序的一样,都为O(n^2)。

摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一

基本流程

  1. 给定待排序数组data[N]
  2. 如果N <= 1, 直接返回
  3. 视data[0]为已排序状态
  4. 从位置1开始, "插入"到左侧已排序区
  5. 类似冒泡排序, 将data[1]与其前方的数字两两比较
  6. 因为左侧是已排序区, 因此遇到首个更小的数字, 本轮比较即可终止, 前面的数字只会更小, 无需比较了.
  7. 循环4-6, 直到右侧数字全部插入左侧区域, 完成排序

改进流程(二分插入)

  1. 由于左侧区域是已排序区域, 因此可以使用二分查找法, 快速定位插入位置
  2. 给定左侧已排序的区域data[0:N]
  3. 现准备插入data[N+1]
  4. 初始状态, 设置left=0, right = N-1
  5. 取位置p = (left + right) / 2
  6. 比较data[p]与data[N+1]
  7. 如果data[p] == data[N+1], 则直接插入位置p
  8. 如果data[p]更大, 说明目标位置还在更左, 则设置right = p - 1
  9. 如果data[p]更小, 说明目标位置还在更右, 则设置left = p + 1
  10. 循环5-9步, 直到left,right收敛为同一位置
  11. 比较data[left]与data[N+1], 小于等于则插入left+1位置, 否则插入left位置
  12. 循环3-11步, 直到右侧数字全部插入左侧区域, 完成排序

目标

  • 实现并验证普通插入排序, 二分插入排序, 并观察两者效率差异
  • 通过定义比较函数兼容任意值类型
  • 通过调整比较函数实现倒序输出

设计

  • ISorter: 定义排序算法接口, 以及值比较函数
  • tInsertSort: 实现插入排序, 通过参数控制, 切换插入函数, 分别实现普通插入和二分插入

单元测试

insert_sort_test.go, 测试过程与选择排序基本一致
从测试结果看, 普通插入排序比选择排序快约30%, 二分插入又比普通插入快近四倍.

package sorting

import (
    "fmt"
    "learning/gooop/sorting"
    "learning/gooop/sorting/insert_sort"
    "math/rand"
    "testing"
    "time"
)

func Test_InsertSort(t *testing.T) {
    fnAssertTrue := func(b bool, msg string) {
        if !b {
            t.Fatal(msg)
        }
    }

    reversed := false
    fnCompare := func(a interface{}, b interface{}) sorting.CompareResult {
        i1 := a.(int)
        i2 := b.(int)

        if i1 < i2 {
            if reversed {
                return sorting.GREATER
            } else {
                return sorting.LESS
            }
        } else if i1 == i2 {
            return sorting.EQUAL
        } else {
            if reversed {
                return sorting.LESS
            } else {
                return sorting.GREATER
            }
        }
    }

    fnTestSorter := func(sorter sorting.ISorter) {
        reversed = false

        // test simple array
        samples := []interface{} { 2,3,1 }
        sorter.Sort(samples, fnCompare)
        fnAssertTrue(fmt.Sprintf("%v", samples) == "[1 2 3]",  "expecting 1,2,3")
        t.Log("pass sorting [2 3 1] >> [1 2 3]")

        // test 10000 items sorting
        rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
        sampleCount := 10000
        t.Logf("prepare large array with %v items", sampleCount)
        samples = make([]interface{}, sampleCount)
        for i := 0;i < sampleCount;i++ {
            samples[i] = rnd.Intn(sampleCount*10)
        }

        t.Logf("sorting large array with %v items", sampleCount)
        t0 := time.Now().UnixNano()
        sorter.Sort(samples, fnCompare)
        cost := time.Now().UnixNano() - t0
        for i := 1;i < sampleCount;i++ {
            fnAssertTrue(fnCompare(samples[i-1], samples[i]) != sorting.GREATER, "expecting <=")
        }
        t.Logf("end sorting large array, cost = %v ms", cost / 1000000)

        // test 0-20
        sampleCount = 20
        t.Log("sorting 0-20")
        samples = make([]interface{}, sampleCount)
        for i := 0;i < sampleCount;i++ {
            for {
                p := rnd.Intn(sampleCount)
                if samples[p] == nil {
                    samples[p] = i
                    break
                }
            }
        }
        t.Logf("unsort = %v", samples)

        sorter.Sort(samples, fnCompare)
        t.Logf("sorted = %v", samples)
        fnAssertTrue(fmt.Sprintf("%v", samples) == "[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]", "expecting 0-20")
        t.Log("pass sorting 0-20")

        // test special
        samples = []interface{} {}
        sorter.Sort(samples, fnCompare)
        t.Log("pass sorting []")

        samples = []interface{} { 1 }
        sorter.Sort(samples, fnCompare)
        t.Log("pass sorting [1]")

        samples = []interface{} { 3,1 }
        sorter.Sort(samples, fnCompare)
        fnAssertTrue(fmt.Sprintf("%v", samples) == "[1 3]",  "expecting 1,3")
        t.Log("pass sorting [1 3]")

        reversed = true
        samples = []interface{} { 2, 3,1 }
        sorter.Sort(samples, fnCompare)
        fnAssertTrue(fmt.Sprintf("%v", samples) == "[3 2 1]",  "expecting 3,2,1")
        t.Log("pass sorting [3 2 1]")
    }

    t.Log("\\ntesting SimpleInsertSorter")
    fnTestSorter(insert_sort.SimpleInsertSorter)

    t.Log("\\ntesting BinaryInsertSorter")
    fnTestSorter(insert_sort.BinaryInsertSorter)
}

测试输出

$ go test -v insert_sort_test.go 
=== RUN   Test_InsertSort
    insert_sort_test.go:109: 
        testing SimpleInsertSorter
    insert_sort_test.go:48: pass sorting [2 3 1] >> [1 2 3]
    insert_sort_test.go:53: prepare large array with 10000 items
    insert_sort_test.go:59: sorting large array with 10000 items
    insert_sort_test.go:66: end sorting large array, cost = 188 ms
    insert_sort_test.go:70: sorting 0-20
    insert_sort_test.go:81: unsort = [10 6 14 18 12 11 19 9 17 8 7 0 15 16 1 3 5 13 4 2]
    insert_sort_test.go:84: sorted = [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]
    insert_sort_test.go:86: pass sorting 0-20
    insert_sort_test.go:91: pass sorting []
    insert_sort_test.go:95: pass sorting [1]
    insert_sort_test.go:100: pass sorting [1 3]
    insert_sort_test.go:106: pass sorting [3 2 1]
    insert_sort_test.go:112: 
        testing BinaryInsertSorter
    insert_sort_test.go:48: pass sorting [2 3 1] >> [1 2 3]
    insert_sort_test.go:53: prepare large array with 10000 items
    insert_sort_test.go:59: sorting large array with 10000 items
    insert_sort_test.go:66: end sorting large array, cost = 46 ms
    insert_sort_test.go:70: sorting 0-20
    insert_sort_test.go:81: unsort = [3 19 13 17 11 4 15 10 14 5 6 0 7 2 8 16 18 12 1 9]
    insert_sort_test.go:84: sorted = [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]
    insert_sort_test.go:86: pass sorting 0-20
    insert_sort_test.go:91: pass sorting []
    insert_sort_test.go:95: pass sorting [1]
    insert_sort_test.go:100: pass sorting [1 3]
    insert_sort_test.go:106: pass sorting [3 2 1]
--- PASS: Test_InsertSort (0.24s)
PASS
ok      command-line-arguments  0.237s

ISorter.go

定义排序算法接口, 以及值比较函数

package sorting

type ISorter interface {
    Sort(data []interface{}, comparator CompareFunction) []interface{}
}

type CompareFunction func(a interface{}, b interface{}) CompareResult

type CompareResult int
const LESS CompareResult = -1
const EQUAL CompareResult = 0
const GREATER CompareResult = 1

tInsertSort.go

实现插入排序, 通过参数控制, 切换插入函数, 分别实现普通插入和二分插入

package insert_sort

import (
    "learning/gooop/sorting"
)

type tInsertSort struct {
    fnInsert fnInsertFunction
}

func newInsertSort(usingBinarySearch bool) sorting.ISorter {
    it := &tInsertSort{
        nil,
    }
    if usingBinarySearch {
        it.fnInsert = binaryInsert
    } else {
        it.fnInsert = simpleInsert
    }
    return it
}

type fnInsertFunction func(data []interface{}, from int, to int, comparator sorting.CompareFunction)

// 插入排序
func (me *tInsertSort) Sort(data []interface{}, comparator sorting.CompareFunction) []interface{} {
    if data == nil {
        return nil
    }

    size := len(data)
    if size <= 1 {
        return data
    }

    for i := 1;i < size;i++ {
        me.fnInsert(data, 0, i, comparator)
    }

    return data
}

// 将位于to的值插入到有序序列data的[from,to)位置
func simpleInsert(data []interface{}, from int, to int, comparator sorting.CompareFunction) {
    for i := to;i>from;i-- {
        prev := i - 1
        if comparator(data[prev], data[i]) == sorting.GREATER {
            data[prev], data[i] = data[i], data[prev]
        } else {
            break
        }
    }
}

// 将位于to的值插入到有序序列data的[from,to)位置
func binaryInsert(data []interface{}, from int, to int, comparator sorting.CompareFunction) {
    p := binaryIndexOf(data, from, to - 1, data[to], comparator)
    if p != to {
        v := data[to]
        moveArrayRange(data, p, p + 1, to - p)
        data[p] = v
    }
}

// 整体移动数组的指定范围
func moveArrayRange(data []interface{}, src int, dst int, size int) {
    t := dst + size - 1
    for i := src + size - 1;i >= src;i-- {
        data[t] = data[i]
        t--
    }
}

// 二分查找首个>=v的下标
func binaryIndexOf(data []interface{}, from int, toInclusive int, v interface{}, comparator sorting.CompareFunction) int {
    for {
        if from == toInclusive {
            switch comparator(data[from], v) {
            case sorting.LESS:
                return from + 1

            default:
                return from
            }

        } else {
            p := (from + toInclusive) / 2
            switch comparator(data[p], v) {
            case sorting.LESS:
                from = min(p + 1, toInclusive)
                break

            case sorting.EQUAL:
                return p

            case sorting.GREATER:
                toInclusive = max(from, p - 1)
                break
            }
        }
    }
}

func min(a, b int) int {
    if a <= b {
        return a
    } else {
        return b
    }
}

func max(a, b int) int {
    if a >= b {
        return a
    } else {
        return b
    }
}

var SimpleInsertSorter = newInsertSort(false)
var BinaryInsertSorter = newInsertSort(true)

(end)

以上是关于手撸golang 基本数据结构与算法 插入排序的主要内容,如果未能解决你的问题,请参考以下文章

手撸golang 基本数据结构与算法 选择排序

手撸golang 基本数据结构与算法 归并排序

手撸golang 基本数据结构与算法 快速排序

手撸golang 基本数据结构与算法 堆排序

手撸golang 基本数据结构与算法 二叉查找树

手撸golang 基本数据结构与算法 网页排名/PageRank,随机游走