多种语言实现数据结构之稀疏数组和队列

前言

一部分有工做经验的老司机对数据结构是很熟悉了,而一部分刚参加工做或者刚入行的人对数据结构是略懂一二甚至是感到陌生,但愿本篇文章可让老司机更熟悉数据结构的实现,不懂数据结构的小白对数据结构的实现有必定的了解。javascript

本系列文章使用多种语言实现经常使用的数据结构,包括目前使用最多的 Java,21 世纪兴起的 Go,前端领域的 JavaScript,目的是尽量的让更多的人可以看的懂、看的明白。总体上采用文字、图和代码的方式进行介绍,代码默认使用 Go 语言版本,其它语言请参考完整的源代码,对应的源代码见下文。前端

全部已经更新的内容在 Github 上,若是对你有帮助请给个 star,谢谢 \ (•◡•) /,你的确定是我继续更新的动力 ☺。java

稀疏数组

各类语言实现代码:Go Java JavaScriptgit

默认使用 Go 语言实现。github

介绍

在二维数组中,若是值为 0 的元素数目远远大于非 0 元素的数目,而且非 0 元素的分布没有规律,则该数组被称为稀疏数组。若是非 0 元素数目占大多数,则称该数组为稠密数组。数组的稠密度指的是非零元素的总数比上数组全部元素的总数。golang

下图是一个 0 值远大于非 0 值的二维数组后端

多种语言实现数据结构之稀疏数组和队列

稀疏数组能够看作是一个压缩的数组,稀疏数组的好处有:数组

  • 原数组中存在大量的无效数据,占据了大量的存储空间,真正有用的数据却少之又少
  • 压缩存储能够节省存储空间以免资源的没必要要的浪费,在数据序列化到磁盘时,压缩存储能够提升 IO 效率

采用稀疏数组的存储方式为第一行存储原始数据总行数,总列数,默认值 0,接下来每一行都存储非0数所在行,所在列,和具体值。上图中的二维数组转成稀疏数组后以下:数据结构

多种语言实现数据结构之稀疏数组和队列

下面使用稀疏数组存储上述的二维数组,把稀疏数组保存在文件中,而且能够从新恢复成二维数组。app

建立二维数组并初始化

func printArray(array [5][5]int) {
    for i := 0; i < len(array); i++ {
        for j := 0; j < len(array[i]); j++ {
            fmt.Print(array[i][j], "\t")
        }
        fmt.Print("\n")
    }
}

func main() {
    // 定义一个二维数组
    var array [5][5]int
    // 初始化 3,6, 1,5
    array[0][2] = 3
    array[1][3] = 6
    array[2][1] = 1
    array[3][3] = 5

    fmt.Println("原二维数组:")
    printArray(array)
}

输出:

原二维数组:
0   0   3   0   0   
0   0   0   6   0   
0   1   0   0   0   
0   0   0   5   0   
0   0   0   0   0

二维数组转稀疏数组

// 二维数组转稀疏数组
func toSparseArray(array [5][5]int) [][3]int {
    // Go 语言这样写没法编译经过
    // var sparseArray [count + 1][]int
    // 使用切片来定义
    var sparseArray = make([][3]int, 0)
    sparseArray = append(sparseArray, [3]int{ 5, 5, 0})

    for i := 0; i < len(array); i++ {
        for j := 0; j < len(array[i]); j++ {
            if array[i][j] != 0 {
                // 保存 row, col, val
                sparseArray = append(sparseArray, [3]int{ i, j, array[i][j]})
            }
        }
    }

    return sparseArray
}

func printArray(array [5][5]int) {
    for i := 0; i < len(array); i++ {
        for j := 0; j < len(array[i]); j++ {
            fmt.Print(array[i][j], "\t")
        }
        fmt.Print("\n")
    }
}

func printSparseArray(sparseArray [][3]int) {
    for i := 0; i < len(sparseArray); i++ {
        for j := 0; j < 3; j++ {
            fmt.Print(sparseArray[i][j], "\t")
        }
        fmt.Print("\n")
    }
}

func main() {
    // 定义一个二维数组
    var array [5][5]int
    // 初始化 3,6, 1,5
    array[0][2] = 3
    array[1][3] = 6
    array[2][1] = 1
    array[3][3] = 5

    fmt.Println("原二维数组:")
    printArray(array)

    // 转成稀疏数组
    sparseArray := toSparseArray(array)

    fmt.Println("转换后的稀疏数组:")
    printSparseArray(sparseArray)
}

输出:

原二维数组:
0   0   3   0   0   
0   0   0   6   0   
0   1   0   0   0   
0   0   0   5   0   
0   0   0   0   0   
转换后的稀疏数组:
5   5   0   
0   2   3   
1   3   6   
2   1   1   
3   3   5

存储和读取稀疏数组

var sparseArrayFileName = "./sparse.data"
// 存储稀疏数组
func storageSparseArray(sparseArray [][3]int) {
    file, err := os.Create(sparseArrayFileName)
    defer file.Close()

    if err != nil {
        fmt.Println("建立文件 sparse.data 错误:", err)
        return
    }
    // 存储矩阵格式
    for i := 0 ; i < len(sparseArray); i++ {
        content := ""
        for j := 0; j < 3; j++ {
            content += strconv.Itoa(sparseArray[i][j]) + "\t"
        }
        // 行分隔符
        content += "\n"
        _, err = file.WriteString(content)
        if err != nil {
            fmt.Println("写入内容错误:", err)
        }
    }
}

// 读取稀疏数组
func readSparseArray() [][3]int {
    file, err := os.Open(sparseArrayFileName)
    defer file.Close()

    if err != nil {
        fmt.Println("打开文件 sparse.data 错误:", err)
        return nil
    }
    sparseArray := make([][3]int, 0)
    reader := bufio.NewReader(file)
    for {
        // 分行读取
        content, err := reader.ReadString('\n')
        if err == io.EOF {
            break
        }
        arr := strings.Split(content, "\t")
        row, _ := strconv.Atoi(arr[0])
        col, _ := strconv.Atoi(arr[1])
        val, _ := strconv.Atoi(arr[2])
        sparseArray = append(sparseArray, [3]int { row, col, val})
    }
    return sparseArray
}

func main() {
    // 定义一个二维数组
    var array [5][5]int
    // 初始化 3,6, 1,5
    array[0][2] = 3
    array[1][3] = 6
    array[2][1] = 1
    array[3][3] = 5

    fmt.Println("原二维数组:")
    printArray(array)

    // 转成稀疏数组
    sparseArray := toSparseArray(array)

    fmt.Println("转换后的稀疏数组:")
    printSparseArray(sparseArray)

    // 存储稀疏数组
    storageSparseArray(sparseArray)

    // 读取稀疏数组
    sparseArray = readSparseArray()
    fmt.Println("读取的稀疏数组:")
    printSparseArray(sparseArray)
}

运行以上代码后,打开 sparse.data 文件,内容以下:

5   5   0   
0   2   3   
1   3   6   
2   1   1   
3   3   5

输出:

...

读取的稀疏数组:
5   5   0   
0   2   3   
1   3   6   
2   1   1   
3   3   5

稀疏数组转二维数组

// 稀疏数组转二维数组
func toArray(sparseArray [][3]int) [5][5]int {
    var array [5][5]int

    // 从稀疏数组中第二行开始读取数据
    for i := 1; i < len(sparseArray); i++ {
        row := sparseArray[i][0]
        col := sparseArray[i][1]
        val := sparseArray[i][2]
        array[row][col] = val
    }

    return array
}

func main() {
    // 定义一个二维数组
    var array [5][5]int
    // 初始化 3,6, 1,5
    array[0][2] = 3
    array[1][3] = 6
    array[2][1] = 1
    array[3][3] = 5

    fmt.Println("原二维数组:")
    printArray(array)

    // 转成稀疏数组
    sparseArray := toSparseArray(array)

    fmt.Println("转换后的稀疏数组:")
    printSparseArray(sparseArray)

    // 存储稀疏数组
    storageSparseArray(sparseArray)

    // 读取稀疏数组
    sparseArray = readSparseArray()
    fmt.Println("读取的稀疏数组:")
    printSparseArray(sparseArray)

    // 转成二维数组
    array = toArray(sparseArray)
    fmt.Println("转换后的二维数组:")
    printArray(array)
}

输出:

...

转换后的二维数组:
0   0   3   0   0   
0   0   0   6   0   
0   1   0   0   0   
0   0   0   5   0   
0   0   0   0   0

队列(queue)

各类语言实现代码:Go Java JavaScript

默认使用 Go 语言实现。

介绍

队列是一种特殊的线性表,它只容许在线性表的前端进行删除操做,在表的后端进行插入操做,因此队列又称为先进先出(FIFO—first in first out)的线性表。进行插入操做的一端叫作队尾,进行删除操做的一端叫作队头。队列的数据元素叫作队列元素,在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。

多种语言实现数据结构之稀疏数组和队列

顺序队列

顺序队列相似数组,它须要一块连续的内存,并有两个指针,一个是队头指针 front,它指向队头元素,另外一个是队尾指针 rear,它指向下一个入队的位置。在队尾插入一个元素时,队尾指针加一,在队头删除一个元素时,队头指针加一。不断的进行插入和删除操做,队列元素在不断的变化,当队头指针等于队尾指针时,队列中没有任何元素,没有元素的队列称为空队列。对于已经出队列的元素所占用的空间,顺序队列没法再次利用。

队列能够使用数组结构或者链表结构来实现,这里使用数组来实现队列。

用数组来实现顺序队列的思路:

  • 定义数组,存储队列元素
  • 定义队列最大大小 maxSize
  • 定义队头指针 front,初始化为 0
  • 队尾指针 rear,初始化为 0
  • 入队方法 put
  • 出队方法 take

定义顺序队列结构体和建立结构体实例函数:

type IntQueue struct {
    array []int // 存放队列元素的切片(数组没法使用变量来定义长度)
    maxSize int // 最大队列元素大小
    front int // 队头指针
    rear int // 队尾指针
}

func NewQueue(size int) *IntQueue {
    return &IntQueue{
        array:   make([]int, size),
        maxSize: size,
        front:   0,
        rear:    0,
    }
}

入队方法:

func (q *IntQueue) Put(elem int) error {
    // 队尾指针不能超过最大队列元素大小
    if q.rear >= q.maxSize {
        return errors.New("queue is full")
    }
    q.array[q.rear] = elem
    q.rear++ // 队尾指针加一
    return nil
}

出队方法:

func (q *IntQueue) Take() (int, error) {
    // 队头指针等于队尾指针表示队列为空
    if q.front == q.rear {
        return 0, errors.New("queue is empty")
    }
    elem := q.array[q.front]
    q.front++ // 队头指针加一
    return elem, nil
}

为了方便查看输出结果,从新定义 String 方法:

// 从新定义 String 方法,方便输出
func (q *IntQueue) String() string {
    str := "["
    for i := q.front; i < q.rear; i++ {
        str += strconv.Itoa(q.array[i]) + " "
    }
    str += "]"
    return str
}

测试代码以下:

func main() {
    intQueue := NewQueue(3)
    _ = intQueue.Put(1)
    _ = intQueue.Put(2)
    _ = intQueue.Put(3)
    _ = intQueue.Put(4) // 队列已满,没法放入数据

    fmt.Println("intQueue:", intQueue)

    num, _ := intQueue.Take()
    fmt.Println("取出一个元素:", num)
    num, _ = intQueue.Take()
    fmt.Println("取出一个元素:", num)
    num, _ = intQueue.Take()
    fmt.Println("取出一个元素:", num)
    num, takeErr := intQueue.Take()
    fmt.Println("取出一个元素:", num)
    if takeErr != nil {
        fmt.Println("出队失败:", takeErr)
    }

    // 此时队列已经用完,没法放数据
    putErr := intQueue.Put(4)
    if putErr != nil {
        fmt.Println("入队失败:", putErr)
    }
    fmt.Println("intQueue:", intQueue)
}

测试以上代码,输出:

intQueue: [1 2 3 ]
取出一个元素: 1
取出一个元素: 2
取出一个元素: 3
取出一个元素: 0
出队失败: queue is empty
入队失败: queue is full
intQueue: []

循环队列

在实际使用队列时,顺序队列空间不能重复使用,须要对顺序队列进行改进。无论是插入或删除,一旦 rear 指针增 1 或 front 指针增 1 时超出了队列所分配的空间,就让它指向起始位置。当 MaxSize - 1增 1变到 0,可用取余运算获取队头或者队尾指针增长 1 后的位置,队尾指针计算方法为 rear % MaxSize,队尾指针计算方法为 front % MaxSize。这种循环使用队列空间的队列称为循环队列。除了一些简单应用以外,真正实用的队列是循环队列。

使用数组实现循环队列思路:

  • 定义数组,存储队列元素
  • 定义队列最大大小 maxSize
  • 定义队头指针 front,初始化为 0,删除元素后,从新计算值,计算公式为:(front + 1) % maxSize
  • 队尾指针 rear,初始化为 0,插入元素后,从新计算值,计算公式为:(q.rear + 1) % q.maxSize
  • 判断队列是否为空的方法,判断 front 等于 rear 判断便可
  • 判断队列是否已满的方法,判断 (rear + 1) % maxSize 是否等于 front
  • 获取队列元素大小方法,计算公式:(rear + maxSize - front) % maxSize
  • 入队方法 put
  • 出队方法 take

定义循环队列结构体和建立结构体实例函数:

type IntQueue struct {
    array []int // 存放队列元素的切片(数组没法使用变量来定义长度)
    maxSize int // 最大队列元素大小
    front int // 队头指针
    rear int // 队尾指针
}

func NewQueue(size int) *IntQueue {
    return &IntQueue{
        array:   make([]int, size),
        maxSize: size,
        front:   0,
        rear:    0,
    }
}

判断队列是否为空:

func (q *IntQueue) isEmpty() bool {
    // 队头指针等于队尾指针表示队列为空
    return q.front == q.rear
}

判断队列是否已满:

func (q *IntQueue) isFull() bool {
    // 空出一个位置,判断是否等于队头指针
    // 队尾指针指向的位置不能存放队列元素,实际上会比 maxSize 指定的大小少一
    return (q.rear + 1) % q.maxSize == q.front
}

获取队列元素大小:

func (q *IntQueue) size() int {
    return (q.rear + q.maxSize - q.front) % q.maxSize
}

入队方法:

func (q *IntQueue) Put(elem int) error {
    if q.isFull() {
        return errors.New("queue is full")
    }
    q.array[q.rear] = elem
    // 循环累加,当 rear + 1 等于 maxSize 时变成 0,从新累加
    q.rear = (q.rear + 1) % q.maxSize
    return nil
}

出队方法:

func (q *IntQueue) Take() (int, error) {
    if q.isEmpty() {
        return 0, errors.New("queue is empty")
    }
    elem := q.array[q.front]
    q.front = (q.front + 1) % q.maxSize
    return elem, nil
}

从新定义的 String 方法:

func (q *IntQueue) String() string {
    str := "["
    tempFront := q.front
    for i := 0; i < q.size(); i++ {
        str += strconv.Itoa(q.array[tempFront]) + " "
        // 超过最大大小,从 0 开始
        tempFront = (tempFront + 1 ) % q.maxSize
    }
    str += "]"
    return str
}

测试代码以下:

func main() {
    intQueue := NewQueue(5)
    _ = intQueue.Put(1)
    _ = intQueue.Put(2)
    _ = intQueue.Put(3)
    _ = intQueue.Put(4)
    _ = intQueue.Put(5) // 队列已满,没法放入数据,实际上只能放 4 个元素

    fmt.Println("intQueue:", intQueue)

    num, _ := intQueue.Take()
    fmt.Println("取出一个元素:", num)
    num, _ = intQueue.Take()
    fmt.Println("取出一个元素:", num)
    num, _ = intQueue.Take()
    fmt.Println("取出一个元素:", num)
    num, _ = intQueue.Take()
    fmt.Println("取出一个元素:", num)
    num, takeErr := intQueue.Take()
    fmt.Println("取出一个元素:", num)
    if takeErr != nil {
        fmt.Println("出队失败:", takeErr)
    }

    // 取出数据后能够继续放入数据
    _ = intQueue.Put(5)
    fmt.Println("intQueue:", intQueue)
}

测试以上代码,输出:

intQueue: [1 2 3 4 ]
取出一个元素: 1
取出一个元素: 2
取出一个元素: 3
取出一个元素: 4
取出一个元素: 0
出队失败: queue is empty
intQueue: [5 ]
相关文章
相关标签/搜索