Manual golang basic data structure and algorithm insertion sorting

Time:2021-10-27

origin

Recently read < < my first algorithm book > > ([Japan] Ishida Baohui; Miyazaki Xiuyi)
This series of notes is intended to use golang exercises

Insert sort

Insertion sort is an algorithm to sort data from the left end of the sequence.

In the sorting process, the data on the left is returned one after another,
The data left on the right is the data that has not been sorted.

The idea of inserting sorting is to take out a data from the unordered area on the right,
Then insert it into the appropriate position in the sorted area.

The time complexity is the same as that of bubble sorting, which is O (n ^ 2).

From < < my first algorithm book > > [Japan] Ishida Baohui; Miyazaki Xiuyi

Basic process

  1. Given array to be sorted data [n]
  2. If n < = 1, return directly
  3. Treat data [0] as sorted
  4. Starting at position 1, “insert” to the sorted area on the left
  5. Similar to bubble sorting, compare data [1] with the number in front of it
  6. Because the sorted area is on the left, this round of comparison can be terminated when the first smaller number is encountered. The previous number will only be smaller and there is no need to compare
  7. Cycle 4-6 until all the numbers on the right are inserted into the left area to complete the sorting

Improvement process (binary insertion)

  1. Since the left area is a sorted area, you can use the binary search method to quickly locate the insertion position
  2. Given the sorted area data [0: n] on the left
  3. Ready to insert data [n + 1]
  4. Initial state, set left = 0, right = n-1
  5. Take position P = (left + right) / 2
  6. Compare data [P] with data [n + 1]
  7. If data [P] = = data [n + 1], insert position P directly
  8. If the data [P] is larger, it means that the target position is still more left, set right = P – 1
  9. If the data [P] is smaller, it means that the target position is still more right, set left = P + 1
  10. Cycle 5-9 steps until left and right converge to the same position
  11. Compare data [left] with data [n + 1]. If it is less than or equal to, insert it into the left + 1 position; otherwise, insert it into the left position
  12. Cycle steps 3-11 until all the numbers on the right are inserted into the left area to complete the sorting

target

  • Implement and verify the ordinary insertion sort and binary insertion sort, and observe the efficiency difference between them
  • Compatibility with arbitrary value types by defining comparison functions
  • The reverse order output is realized by adjusting the comparison function

Design

  • Isorter: defines the sorting algorithm interface and the value comparison function
  • Tinsertsort: realize insertion sorting, switch insertion functions through parameter control, and realize ordinary insertion and binary insertion respectively

unit testing

insert_ sort_ Test.go, the test process is basically consistent with the selection and sorting
From the test results, ordinary insertion sorting is about 30% faster than selective sorting, and binary insertion is nearly four times faster than ordinary insertion

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)
}

Test output

$ 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

Define sorting algorithm interface and value comparison function

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

Realize insertion sorting, switch the insertion function through parameter control, and realize ordinary insertion and binary insertion respectively

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)

//Insert sort
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
}

//Insert the value at to into the [from, to) position of the ordered sequence data
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
        }
    }
}

//Insert the value at to into the [from, to) position of the ordered sequence data
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
    }
}

//Moves the specified range of the array as a whole
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--
    }
}

//Binary search for the first subscript > = 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)