咱們翻閱書籍時,不少時候都要查找目錄,而後定位到咱們要的頁數,好比咱們查找某個英文單詞時,會從英語字典裏查看單詞表目錄,而後定位到詞的那一頁。算法
計算機中,也有這種需求。編程
字典是存儲鍵值對的數據結構,把一個鍵和一個值映射起來,一一映射,鍵不能重複。在某些教程中,這種結構可能稱爲符號表,關聯數組或映射。咱們暫且稱它爲字典,較好理解。segmentfault
如:數組
鍵=>值 "cat"=>2 "dog"=>1 "hen"=>3
咱們拿出鍵cat
的值,就是2
了。安全
Golang
提供了這一數據結構:map
,而且要求鍵的數據類型必須是可比較的,由於若是不可比較,就沒法知道鍵是存在仍是不存在。數據結構
Golang
字典的通常的操做以下:併發
package main import "fmt" func main() { // 新建一個容量爲4的字典 map m := make(map[string]int64, 4) // 放三個鍵值對 m["dog"] = 1 m["cat"] = 2 m["hen"] = 3 fmt.Println(m) // 查找 hen which := "hen" v, ok := m[which] if ok { // 找到了 fmt.Println("find:", which, "value:", v) } else { // 找不到 fmt.Println("not find:", which) } // 查找 ccc which = "ccc" v, ok = m[which] if ok { // 找到了 fmt.Println("find:", which, "value:", v) } else { // 找不到 fmt.Println("not find:", which) } }
字典的實現有兩種方式:哈希表HashTable
和紅黑樹RBTree
。Golang
語言中字典map
的實現由哈希表實現,具體可參考標準庫runtime
下的map.go
文件。app
咱們會在《查找算法》章節:散列查找和紅黑樹中,具體分析字典的兩種實現方式。數據結構和算法
通常不少編程語言庫,會把不可重複集合(Collection
)命名爲Set
,這個Set
中文直譯爲集合,在某些上下文條件下,咱們大腦要自動過濾,集合這詞指的是不可重複集合仍是指統稱的集合,在這裏均可以看到中文博大精深。編程語言
不可重複集合Set
存放數據,特色就是沒有數據會重複,會去重。你放一個數據進去,再放一個數據進去,若是兩個數據同樣,那麼只會保存一份數據。
集合Set
能夠沒有順序關係,也能夠按值排序,算一種特殊的列表。
由於咱們知道字典的鍵是不重複的,因此只要咱們不考慮字典的值,就能夠實現集合,咱們來實現存整數的集合Set
:
// 集合結構體 type Set struct { m map[int]struct{} // 用字典來實現,由於字段鍵不能重複 len int // 集合的大小 sync.RWMutex // 鎖,實現併發安全 }
// 新建一個空集合 func NewSet(cap int64) *Set { temp := make(map[int]struct{}, cap) return &Set{ m: temp, } }
使用一個容量爲cap
的map
來實現不可重複集合。map
的值咱們不使用,因此值定義爲空結構體struct{}
,由於空結構體不佔用內存空間。如:
package main import ( "fmt" "sync" ) func main() // 爲何使用空結構體 a := struct{}{} b := struct{}{} if a == b { fmt.Printf("right:%p\n", &a) } fmt.Println(unsafe.Sizeof(a)) }
會打印出:
right:0x1198a98 0
空結構體的內存地址都同樣,而且不佔用內存空間。
// 增長一個元素 func (s *Set) Add(item int) { s.Lock() defer s.Unlock() s.m[item] = struct{}{} // 實際往字典添加這個鍵 s.len = len(s.m) // 從新計算元素數量 }
首先,加併發鎖,實現線程安全,而後往結構體s *Set
裏面的內置map
添加該元素:item
,元素做爲字典的鍵,會自動去重。同時,集合大小從新生成。
時間複雜度等於字典設置鍵值對的複雜度,哈希不衝突的時間複雜度爲:O(1)
,不然爲O(n)
,可看哈希表實現一章。
// 移除一個元素 func (s *Set) Remove(item int) { s.Lock() s.Unlock() // 集合沒元素直接返回 if s.len == 0 { return } delete(s.m, item) // 實際從字典刪除這個鍵 s.len = len(s.m) // 從新計算元素數量 }
同理,先加併發鎖,而後刪除map
裏面的鍵:item
。時間複雜度等於字典刪除鍵值對的複雜度,哈希不衝突的時間複雜度爲:O(1)
,不然爲O(n)
,可看哈希表實現一章。
// 查看是否存在元素 func (s *Set) Has(item int) bool { s.RLock() defer s.RUnlock() _, ok := s.m[item] return ok }
時間複雜度等於字典獲取鍵值對的複雜度,哈希不衝突的時間複雜度爲:O(1)
,不然爲O(n)
,可看哈希表實現一章。
// 查看集合大小 func (s *Set) Len() int { return s.len }
時間複雜度:O(1)
。
// 集合是夠爲空 func (s *Set) IsEmpty() bool { if s.Len() == 0 { return true } return false }
時間複雜度:O(1)
。
// 清除集合全部元素 func (s *Set) Clear() { s.Lock() defer s.Unlock() s.m = map[int]struct{}{} // 字典從新賦值 s.len = 0 // 大小歸零 }
將原先的map
釋放掉,而且從新賦一個空的map
。
時間複雜度:O(1)
。
func (s *Set) List() []int { s.RLock() defer s.RUnlock() list := make([]int, 0, s.len) for item := range s.m { list = append(list, item) } return list }
時間複雜度:O(n)
。
package main import ( "fmt" "sync" "unsafe" ) // 集合結構體 type Set struct { m map[int]struct{} // 用字典來實現,由於字段鍵不能重複 len int // 集合的大小 sync.RWMutex // 鎖,實現併發安全 } // 新建一個空集合 func NewSet(cap int64) *Set { temp := make(map[int]struct{}, cap) return &Set{ m: temp, } } // 增長一個元素 func (s *Set) Add(item int) { s.Lock() defer s.Unlock() s.m[item] = struct{}{} // 實際往字典添加這個鍵 s.len = len(s.m) // 從新計算元素數量 } // 移除一個元素 func (s *Set) Remove(item int) { s.Lock() s.Unlock() // 集合沒元素直接返回 if s.len == 0 { return } delete(s.m, item) // 實際從字典刪除這個鍵 s.len = len(s.m) // 從新計算元素數量 } // 查看是否存在元素 func (s *Set) Has(item int) bool { s.RLock() defer s.RUnlock() _, ok := s.m[item] return ok } // 查看集合大小 func (s *Set) Len() int { return s.len } // 清除集合全部元素 func (s *Set) Clear() { s.Lock() defer s.Unlock() s.m = map[int]struct{}{} // 字典從新賦值 s.len = 0 // 大小歸零 } // 集合是夠爲空 func (s *Set) IsEmpty() bool { if s.Len() == 0 { return true } return false } // 將集合轉化爲列表 func (s *Set) List() []int { s.RLock() defer s.RUnlock() list := make([]int, 0, s.len) for item := range s.m { list = append(list, item) } return list } // 爲何使用空結構體 func other() { a := struct{}{} b := struct{}{} if a == b { fmt.Printf("right:%p\n", &a) } fmt.Println(unsafe.Sizeof(a)) } func main() { //other() // 初始化一個容量爲5的不可重複集合 s := NewSet(5) s.Add(1) s.Add(1) s.Add(2) fmt.Println("list of all items", s.List()) s.Clear() if s.IsEmpty() { fmt.Println("empty") } s.Add(1) s.Add(2) s.Add(3) if s.Has(2) { fmt.Println("2 does exist") } s.Remove(2) s.Remove(3) fmt.Println("list of all items", s.List()) }
打印出:
list of all items [1 2] empty 2 does exist list of all items [1]
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。