• 数组


    数组

    数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据

    线性表(Linear List)

    • 线性表就是数据排成像一条线一样的结构
    • 每个线性表上的数据最多只有前和后两个方向
    • 数组链表队列等都是线性表结构

    image.png

    与它相对应的概念是非线性表,比如二叉树等。非线性表指数据之间不是简单的前后关系

    image.png

    连续的内存空间和相同类型的数据

    正因为这两个限制,它才具有“随机访问

    数组是如何实现根据下标随机访问数组元素?

    比如长度为10的int类型数组 int[] = new int[10]

    计算机给数组a[10],分配一块连续的内存空间 1000 ~ 1039

    其中内存块的首地址是 base_address = 1000

    image.png

    我们知道,计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据

    当计算机需要随机访问数组中的某个元素时,它会首先通过寻址公式。

    来计算出该元素存储的内存地址

    a[i]_address = base_address + i * data_type_size

    其中 data_type_size 表示数组中的每个元素的大小

    面试题:数组和链表的区别?

    数组支持随机访问,根据下标随机访问的时间复杂度为 O(1),二分查找的时间复杂度是 O(logn)
    链表适合插入、删除,时间复杂度 O(1)

    低效的“插入”

    如果在数组的末尾插入元素,不需要移动数据,时间复杂度为 O(1)

    在数组的开头插入元素,所有的数据都需要依次往后移动一位,最坏时间复杂度是 O(n)

    因为我们在每个位置插入元素的概率是一样的,所以平均情况时间复杂度为(1+2+...+n)/n = O(n)

    这里考虑一下,如果数组是有序的,在某个位置插入一个新的元素,就必须移动数据

    但是数组中存储的数据并没有任何规律,数组只是作为一个存储数据的集合,在这种情况下

    将插入一个新的元素,为了避免大规模的数据搬移,

    可以直接将第k位的数据移动到数组元素的最后,把新的元素直接放入第k个位置

    image.png

    利用这种处理技巧,在特定情况下,在第k个位置插入一个元素的时间复杂度就会降为O(1),快排中使用

    低效的删除

    跟插入数据类似,如果我们要删除第k个位置的数据,为了内存的连续性,也要搬移数据

    • 如果删除数组末尾的数据,则最好情况时间复杂度为 O(1)
    • 如果删除数组开头的数据,则最坏情况时间复杂度为 O(n)
    • 平均情况时间复杂度为 O(n)

    某些特殊场景下,不一定非得追求数组中数据的连续性。

    如果我们将多次删除操作集中在一起执行,删除的效率会有所提高

    例如,数组a[10]中存储了8个元素:a, b, c, d, e, f, g, h。现在要依次删除 a, b, c 三个元素

    image.png

    为了避免 d, e, f, g, h 这几个数据会被搬移三次,可以先记录下已经删除的数据

    每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除

    当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,

    这样就大大减少了删除操作导致的数据搬移

    这就是 JVM 标记清除垃圾回收算法的核心思想

    数据结构和算法的魅力在于,很多时候并不是要去死记硬背某个数据结构或者算法,
    而是要学习它背后的思想和处理技巧,这些东西才是最有价值的
    不管在软件开发还是架构设计中,总能找到某些算法和数据结构的影子

    警惕数组的访问越界问题

    首先分析这段C语言代码的运行结果

    int main(int argc, char* argv[1]) {
    	int i = 0;
      int arr[3] = {0};
    	for (; i <= 3; i++){
      	arr[i] = 0;
        printf("hello world
    ");
      }
      return 0;
    }
    

    数组大小为3,a[0], a[1], a[2],for 循环的结束条件错写为 i <= 3而非 i < 3,所以当 i=3 时,数组 a[3] 访问越界


    Go语言本身就会越界检查,比如下面代码

    就会抛出 invalid array index 3 (out of bounds for 3-element array)

    	a := [3]int{1, 2, 3}
    	a[3] = 10
    

    容器能否完全代替数组?

    针对数组类型,很多语言都提供了容器类,比如Java中的 ArrayList,C++ STL中的 vector

    在项目开发中,什么时候适合用数组,什么时候适合用容器呢

    • 如果数据大小事先已知,并对数组的操作非常简单,可以直接使用数组

    二维数组的内存寻址公式

    对于m *n 的数组,a[i][j] (i < m, j < n)的地址为:

    a[i][j]_address = base_address + (i*n +j)*type_size

    Go语言代码示例

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    /**
     * int类型数组的插入、删除、根据下标随机访问
     */
    
    type Array struct {
    	data   []int
    	length uint
    }
    
    // 为数组初始化内存
    func NewArray(capacity uint) *Array {
    	if capacity == 0 {
    		return nil
    	}
    	return &Array{
    		data:   make([]int, capacity, capacity),
    		length: 0,
    	}
    }
    
    // 求数组长度
    func (this *Array) Len() uint {
    	return this.length
    }
    
    // 判断索引是否越界
    func (this *Array) isIndexOutOfRange(index uint) bool {
    	if index >= uint(cap(this.data)) {
    		return true
    	}
    	return false
    }
    
    // 通过索引查找数组,索引范围[0, n-1]
    func (this *Array) Find(index uint) (int, error) {
    	if this.isIndexOutOfRange(index) {
    		return 0, errors.New("out of index range")
    	}
    	return this.data[index], nil
    }
    
    // 插入数组到索引index上
    func (this *Array) Insert(index uint, v int) error {
    	if this.Len() == uint(cap(this.data)) {
    		return errors.New("full array")
    	}
    	if index != this.length && this.isIndexOutOfRange(index) {
    		return errors.New("out of index range")
    	}
    	for i := this.length; i > index; i-- {
    		this.data[i] = this.data[i-1]
    	}
    	this.data[index] = v
    	this.length++
    	return nil
    }
    
    // 插入新元素到数组尾部
    func (this *Array) InsertToTail(v int) error {
    	return this.Insert(this.Len(), v)
    }
    
    // 删除索引index上的值
    func (this *Array) Delete(index uint) (int, error) {
    	if this.isIndexOutOfRange(index) {
    		return 0, errors.New("out of index range")
    	}
    	v := this.data[index]
    	for i := index; i < this.Len()-1; i++ {
    		this.data[i] = this.data[i+1]
    	}
    	this.length--
    	return v, nil
    }
    
    // 打印数组
    func (this *Array) Print() {
    	var format string
    	for i := uint(0); i < this.Len(); i++ {
    		format += fmt.Sprintf("|%+v", this.data[i])
    	}
    	fmt.Println(format)
    }
    

    测试示例

    package main
    
    import "testing"
    
    func TestArray_Insert(t *testing.T) {
    	capacity := 10
    	arr := NewArray(uint(capacity))
    	for i := 0; i < capacity-2; i++ {
    		err := arr.Insert(uint(i), i+1)
    		if nil != err {
    			t.Fatal(err.Error())
    		}
    	}
    	arr.Print() // |1|2|3|4|5|6|7|8
    
    	arr.Insert(uint(6), 999)
    	arr.Print() // |1|2|3|4|5|6|999|7|8
    
    	arr.InsertToTail(666)
    	arr.Print() // |1|2|3|4|5|6|999|7|8|666
    }
    
    func TestArray_Delete(t *testing.T) {
    	capacity := 10
    	arr := NewArray(uint(capacity))
    	for i := 0; i < capacity; i++ {
    		err := arr.Insert(uint(i), i+1)
    		if nil != err {
    			t.Fatal(err.Error())
    		}
    	}
    	arr.Print()
    
    	for i := 9; i >= 0; i-- {
    		_, err := arr.Delete(uint(i))
    		if nil != err {
    			t.Fatal(err)
    		}
    		arr.Print()
    	}
    }
    
    func TestArray_Find(t *testing.T) {
    	capacity := 10
    	arr := NewArray(uint(capacity))
    	for i := 0; i < capacity; i++ {
    		err := arr.Insert(uint(i), i+1)
    		if nil != err {
    			t.Fatal(err.Error())
    		}
    	}
    	arr.Print()
    
    	t.Log(arr.Find(0))  // 1 <nil>
    	t.Log(arr.Find(9))  // 10 <nil>
    	t.Log(arr.Find(11)) // 0 out of index range
    }
    

    运行结果

    === RUN   TestArray_Insert
    |1|2|3|4|5|6|7|8
    |1|2|3|4|5|6|999|7|8
    |1|2|3|4|5|6|999|7|8|666
    --- PASS: TestArray_Insert (0.00s)
    === RUN   TestArray_Delete
    |1|2|3|4|5|6|7|8|9|10
    |1|2|3|4|5|6|7|8|9
    |1|2|3|4|5|6|7|8
    |1|2|3|4|5|6|7
    |1|2|3|4|5|6
    |1|2|3|4|5
    |1|2|3|4
    |1|2|3
    |1|2
    |1
    
    --- PASS: TestArray_Delete (0.00s)
    === RUN   TestArray_Find
    |1|2|3|4|5|6|7|8|9|10
    --- PASS: TestArray_Find (0.00s)
        array_test.go:54: 1 <nil>
        array_test.go:55: 10 <nil>
        array_test.go:56: 0 out of index range
    PASS
    
  • 相关阅读:
    erwin逆向工程,logical模型列名修改为中文
    [Leetcode] Two pointer-- 76. Minimum Window Substring
    [Leetcode] Binary search tree -- 173. Binary Search Tree Iterator
    [Leetcode] 684. Redundant Connection
    [Leetcode] Binary tree--653. Two Sum IV
    [Leetcode] Binary tree -- 617. Merge Two Binary Trees
    [Leetcode] Binary tree-- 563. Binary Tree Tilt
    [Leetcode] Binary tree-- 572. Subtree of Another Tree
    [Leetcode] Binary tree-- 437. Path Sum III
    [Leetcode] Binary tree-- 113. Path Sum II
  • 原文地址:https://www.cnblogs.com/custer/p/11653715.html
Copyright © 2020-2023  润新知