順序表


順序表

在程序中,經常需要將一組(通常是同為某個類型的)數據元素作為整體管理和使用,需要創建這種元素組,用變量記錄它們,傳進傳出函數等。一組數據中包含的元素個數可能發生變化(可以增加或刪除元素)。

對於這種需求,最簡單的解決方案便是將這樣一組元素看成一個序列,用元素在序列里的位置和順序,表示實際應用中的某種有意義的信息,或者表示數據之間的某種關系。

這樣的一組序列元素的組織形式,我們可以將其抽象為線性表。一個線性表是某類元素的一個集合,還記錄着元素之間的一種順序關系。線性表是最基本的數據結構之一,在實際程序中應用非常廣泛,它還經常被用作更復雜的數據結構的實現基礎。

根據線性表的實際存儲方式,分為兩種實現模型:

  • 順序表,將元素順序地存放在一塊連續的存儲區里,元素間的順序關系由它們的存儲順序自然表示。
  • 鏈表,將元素存放在通過鏈接構造起來的一系列存儲塊中。

 

順序表的基本形式

int 1,2,3,4,5

現在我們要存儲這幾個連續的整型;
列表,元組,字典等屬於python中的高級數據結構,現在我們研究的是存儲的本質,所以不用這些高級結構;
這里我們要使用的是內存存儲,計算機中的內存時用來存儲數據以及和cpu打交道的;

位,比特bit,字節Byte,字之間的關系

字節、字、位、比特之間的關系是:

1位=1bit比特;1Byte字節=8位;1字=2Byte字節;1字=16位。

bit、byte、位、字節、漢字的關系

1 bit = 1  二進制數據
1 byte  = 8  bit
1 字母 = 1  byte = 8 bit
1 漢字 = 2  byte = 16 bit

1. bit:位

一個二進制數據0或1,是1bit;

2. byte:字節

存儲空間的基本計量單位,如:MySQL中定義 VARCHAR(45) 即是指 45個字節;
1 byte = 8 bit

3. 一個英文字符占一個字節;

1 字母 = 1 byte = 8 bit

4. 一個漢字占2個字節;

1 漢字 = 2 byte = 16 bit
中文字符和編碼有關系的,utf-8編碼下,是3個字節;

計算機中的存儲單位中最小的單位是——位 bit (比特)(Binary Digits),一個位存放一位二進制數,即 0 或 1,它是計算機存儲中最小的存儲單位。

順序表的基本形式

內存的基本單位是1Byte;
內存是一個連續的存儲空間;

int a = 1 

在內存中存儲時,占了四個存儲單元,即占了4*8bit;
一個字符即一個char占一個字節存儲單元;
基礎數據類型不同,所占用的存儲單元也不同;
在存儲的時候,如果存儲的是數字,那么在取連續的四個存儲單元是會當做一個整體取出然后轉換為一個數字;如果存儲的是一個字符,那么取的時候會當做四個字符取出;
存儲數字1的時候,會在內存中存儲 00000000 00000000 00000000 00000001;

0x01 00000000
0x02 00000000
0x03 00000000
0x04 00000001

在存儲一組數據 1,2,3 時,我們要將這三個數字在內存中連續的存儲,以方便取出;
那么就是

0x01 1
0x05 2
0x09 3

這樣存儲的;
我們可以這樣理解,當我們存儲一個純數字列表的時候,存儲了列表第一個元素在內存中的初始位置,當我們要按照下標去取數據的時候,因為數據在內存中是連續存儲的,假設存儲的第一個數據是0x01,那么第二個數據就是 0x0(1+4),第三個數據是 0x0(1+4*2),即按照數據的下標進行數據偏移即可;
所以當一組數據全部是相同數據類型時,我們按照一組數據在內存中緊靠在一起存放,那么這種數據存儲形式就叫做順序表;

 

編程語言中的索引從0開始的原因是存儲數據時,變量指向的是第一個元素的內存位置,它的偏移量為0,即索引的0代表的就是偏移量為0;

數據存儲的基本布局

當我們要存儲一個包含4個整型的列表時,想操作系統申請4個連續的內存,往內存中放置數據;因為是整型,所以一個數據占用四個存儲單元;而我們變量中存儲的就是第一個元素的內存地址,第一個元素偏移量為0,當我們根據索引獲取數據時,就是通過使用第一個元素的內存地址加上索引的偏移量來計算要獲取數據的內存地址的。但應該注意,這是基本布局,存儲的數據類型應該是一樣的,否則因為每個數據類型占用的內存大小不同無法實現這種獲取方式;


元素外置的順序表
因為列表中要存儲不同的數據類型,而基本布局是不能實現的,所以要使用元素外置;
元素外置就是,假設一個列表中存儲的是4個類型不同的數據,我們還是去申請4個連續的內存,但內存中存儲的不再是數據了,而是四個數據的存儲地址;存儲地址占用的大小為4個字節;
然后取數據就和基本布局取數據的方式差不多了;

 

數據表的結構與實現

數據表的結構

順序表的結構分為表頭區和數據區;
當我們要存儲數據時,要向操作系統申請連續的內存,因為要連續,但操作系統並不知道到我們要使用多少的內存大小,所以我們在申請時要先預估存儲多少數據並指定申請的數據數量,這樣操作系統才能分配給我們連續的內存空間;順序表的表頭區存儲的就是 存儲限制數量和已存儲數量;

一個順序表的完整信息包括兩部分,一部分是表中的元素集合,另一部分是為實現正確操作而需記錄的信息,即有關表的整體情況的信息,這部分信息主要包括元素存儲區的容量和當前表中已有的元素個數兩項。

 

數據表的基本實現方式

怎么把表頭區和數據區組合在一起?有兩種方式,一體式和分離式;

一體式就是表頭區和數據區的地址是連續的;
分離式是指表頭區中除了有 存儲限制數量和已存儲數量之外,還存儲有一個指向存儲區地址的內存地址;

兩種基本實現方式

 

 

圖a為一體式結構,存儲表信息的單元與元素存儲區以連續的方式安排在一塊存儲區里,兩部分數據的整體形成一個完整的順序表對象。
一體式結構整體性強,易於管理。但是由於數據元素存儲區域是表對象的一部分,順序表創建后,元素存儲區就固定了。

圖b為分離式結構,表對象里只保存與整個表有關的信息(即容量和元素個數),實際數據元素存放在另一個獨立的元素存儲區里,通過鏈接與基本表對象關聯。

 

如果采用的是一體式,那么當我們要往存儲限制已滿的列表中再添加數據時,因為要采用一體式,並且要擴充數據,如果原來只能存儲4個數據,那么只能再向操作空間申請一個可以存儲2個數據的表頭區和大於4個數據的數據區的連續內存了,然后再將原來的4個數據依次移到新內存中,再添加第5個數據,然后表頭區也要搬移過去,即整體搬遷,可以看出,這是很不方便的;
而分離式結構,雖然也要重新申請連續內存,但只需要申請數據區的內存,然后將表頭區中數據區地址重新指向新的數據區地址即可;
他們之間的主要區別在於,變量中指向的表頭區起始地址是否會發生變化,一體式會重定向,而分離式則不需要;
所以我們一般使用的分離式結構比較多;

 

順序表數據區替換與擴充

替換

一體式結構由於順序表信息區與數據區連續存儲在一起,所以若想更換數據區,則只能整體搬遷,即整個順序表對象(指存儲順序表的結構信息的區域)改變了。
分離式結構若想更換數據區,只需將表信息區中的數據區鏈接地址更新即可,而該順序表對象不變。

擴充

采用分離式結構的順序表,若將數據區更換為存儲空間更大的區域,則可以在不改變表對象的前提下對其數據存儲區進行了擴充,所有使用這個表的地方都不必修改。只要程序的運行環境(計算機系統)還有空閑存儲,這種表結構就不會因為滿了而導致操作無法進行。人們把采用這種技術實現的順序表稱為動態順序表,因為其容量可以在使用中動態變化。

申請數據內存時需要預估數據量;
當存滿了后要擴充數據時要為了減少申請次數,要再申請一部分預留內存,而不是只再額外申請一個數據內存;

擴充的兩種策略

  • 每次擴充增加固定數目的存儲位置,如每次擴充增加10個元素位置,這種策略可稱為線性增長。
  • 特點:節省空間,但是擴充操作頻繁,操作次數多。
  • 每次擴充容量加倍,如每次擴充增加一倍存儲空間。
  • 特點:減少了擴充操作的執行次數,但可能會浪費空間資源。以空間換時間,推薦的方式。

推薦使用加倍擴充,用空間換時間;

 

 

順序表的操作-python列表的實現

數據插入分為三種方式

  • 尾部加入數據;直接在尾部加入數據,很簡單;
  • 非保序的元素插入,比如能存儲8個數據,現有5個數據,我們要插入到索引3,就是將原來第三位的數據放入第6位,再將新數據放入第三位數據;基本不用;
  • 保序的元素插入:比如能存儲8個數據,現有5個數據,我們要插入到索引3,就是將索引3及往后的數據全都往后挪一位,再將新數據插入第3位;插入一個數據后,保持原有的數據順序不同;

刪除數據同理

 

增加元素

如圖所示,為順序表增加新元素111的三種方式

 

 

a. 尾端加入元素,時間復雜度為O(1)
b. 非保序的加入元素(不常見),時間復雜度為O(1)
c. 保序的元素加入,時間復雜度為O(n)

 

刪除元素

 

 

a. 刪除表尾元素,時間復雜度為O(1)
b. 非保序的元素刪除(不常見),時間復雜度為O(1)
c. 保序的元素刪除,時間復雜度為O(n)

 

Python中的順序表

  • Python中的list和tuple兩種類型采用了順序表的實現技術,具有前面討論的順序表的所有性質。
  • tuple是不可變類型,即不變的順序表,因此不支持改變其內部狀態的任何操作,而其他方面,則與list的性質類似。

 

list的基本實現技術

Python中list采用的是分離式順序表和元素外置的存儲結構;動態順序表;前期采用四倍擴充,超過5000采用一倍擴充;

Python標准類型list就是一種元素個數可變的線性表,可以加入和刪除元素,並在各種操作中維持已有元素的順序(即保序),而且還具有以下行為特征:

  • 基於下標(位置)的高效元素訪問和更新,時間復雜度應該是O(1);

為滿足該特征,應該采用順序表技術,表中元素保存在一塊連續的存儲區中。

  • 允許任意加入元素,而且在不斷加入元素的過程中,表對象的標識(函數id得到的值)不變。

為滿足該特征,就必須能更換元素存儲區,並且為保證更換存儲區時list對象的標識id不變,只能采用分離式實現技術。

在Python的官方實現中,list就是一種采用分離式技術實現的動態順序表。這就是為什么用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。
在Python的官方實現中,list實現采用了如下的策略:在建立空表(或者很小的表)時,系統分配一塊能容納8個元素的存儲區;在執行插入操作(insert或append)時,如果元素存儲區滿就換一塊4倍大的存儲區。但如果此時的表已經很大(目前的閥值為50000),則改變策略,采用加一倍的方法。引入這種改變策略的方式,是為了避免出現過多空閑的存儲位置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM