資料結構(三) 資料的基本操作 增刪查

2021-10-10 09:18:01 字數 4209 閱讀 1524

資料最基本的操作—增刪查。

在上篇文章資料結構(二) - 時間複雜度與空間複雜度**現的乙個例子,在乙個陣列中找出出現次數最多的那個元素的數值。例如,輸入陣列 a = [1,2,3,4,5,5,6] 中,只有 5 出現了兩次,其餘都是 1 次。顯然 5 出現的次數最多,則輸出 5。為了降低時間複雜度,引入了 k-v 的字典的資料結構。那麼問題來了,究竟是什麼原因,促使我們想到了使用字典的資料結構呢?如果不使用字典,改為使用陣列行不行呢?

為了回答這些問題,我們先看一下究竟此處**需要對資料進行哪些操作。我們提到過,這段**處理資料的核心思路是:

第一步,根據原始陣列計算每個元素出現的次數;

第二步,根據第一步的結果,找到出現次數最多的元素。

首先,我們來分析第一步統計出現次數的處理。此時,你還不知道應該採用什麼資料結構。

對於每一次的迴圈,你得到了輸入陣列中的某個元素 a[ i ] 。接著,你需要判斷這個元素在未知的資料結構中是否出現過:

如果出現了,就需要對出現的次數加 1。

如果沒有出現過,則把這個元素新增到未知資料結構中,並且把次數賦值為 1。

這裡的資料操作包括以下 3 個。

查詢: 看能否在資料結構中查詢到這個元素,也就是判斷元素是否出現過。

新增: 針對沒有出現過的情況,新增這個元素。

改動: 針對出現過的情況,需要對這個元素出現的次數加 1。

接下來,我們一起分析第二步。訪問資料結構中的每個元素,找到次數最多的元素。這裡涉及的資料操作很簡單,只有查詢。

因此,這段**需要高頻使用查詢的功能。此時,第一步的查詢動作巢狀在 for 迴圈中,如果你的**不能在 o(1) 的時間複雜度內完成,則**整體的時間複雜度並沒有下降。而能在 o(1) 的時間複雜度內完成查詢動作的資料結構,只有字典型別。這樣,外層 for 迴圈是 o(n) 的時間複雜度,內部巢狀的查詢操作是 o(1) 的時間複雜度。整體計算下來,就仍然是 o(n) 的時間複雜度。字典的查詢是通過鍵值對的匹配完成的,它可以在 o(1) 時間複雜度內,實現對數值條件查詢。

現在,我們換個解決方案。假設採用兩個陣列,分別按照對應順序記錄元素及其對應的出現次數。陣列對於元素的查詢只能逐一訪問,時間複雜度是 o(n)。也就是說,在 o(n) 複雜度的 for 迴圈中,又巢狀了 o(n) 複雜度的查詢動作,所以時間複雜度是 o(n²)。因此,這裡的資料結構,只能採用字典型別。

不管是陣列還是字典,都需要額外開闢空間,對資料進行儲存。而且資料儲存的數量,與輸入的資料量一致。因此,消耗的空間複雜度相同,都是 o(n)。由前面的分析可見,同樣採用複雜的資料結構,消耗了 o(n) 的空間複雜度,其對時間複雜度降低的貢獻有可能不一樣。因此,我們必須要設計合理的資料結構,以達到降低時間損耗的目的。

而設計合理的資料結構,又要從問題本身出發,我們可以採用這樣的思考順序:

首先我們分析這段**到底對資料先後進行了哪些操作。

然後再根據分析出來的資料操作,找到合理的資料結構。

這樣我們就把資料處理的基本操作梳理了出來。今後,即使你遇到更複雜的問題,無非就是這些基本操作的疊加和組合。只要按照上述的邏輯進行思考,就可以輕鬆設計出合理的資料結構,

其實,**對資料處理的操作型別非常少。**對資料的處理就是**對輸入資料進行計算,得到結果並輸出的過程。資料處理的操作就是找到需要處理的資料,計算結果,再把結果儲存下來。這個過程總結為以下操作:

找到要處理的資料。這就是按照某些條件進行查詢。

把結果存到乙個新的記憶體空間中。這就是在現有資料上進行新增。

把結果存到乙個已使用的記憶體空間中。這需要先刪除記憶體空間中的已有資料,再新增新的資料。

經過對**的拆解,你會發現即便是很複雜的**,它對資料的處理也只有這 3 個基本操作,增、刪、查。只要你圍繞這 3 個資料處理的操作進行分析,就能得出解決問題的最優方案。常用的分析方法可以參考下面的 3 個步驟:

首先,這段**對資料進行了哪些操作?

其次,這些操作中,哪個操作最影響效率,對時間複雜度的損耗最大?

最後,哪種資料結構最能幫助你提高資料操作的使用效率?

這 3 個步驟構成了設計合理資料結構的方**。圍繞第一步和第二步的資料處理的操作,我會再補充一些例子幫助你理解。而第三個方面就需要你擁有足夠紮實的資料結構基礎知識了,我會在後面的課程中詳細討論。

資料操作與資料結構的案例

我們先來看乙個關於查詢的例子。查詢,就是從複雜的資料結構中,找到滿足某個條件的元素。通常可從以下兩個方面來對資料進行查詢操作:

根據元素的位置或索引來查詢。

根據元素的數值特徵來查詢。

針對上述兩種情況,我們分別給出例子進行詳細介紹。

例 1,我們來看第二個例子,對於乙個陣列,找到陣列中的第二個元素並輸出。

這個問題的處理很簡單。由於陣列本身具有索引 index ,因此直接通過索引就能查詢到其第二個元素。別忘了,陣列的索引值是從 0 開始的,因此第二個元素的索引值是 1 。不難發現,因為有了 index 的索引,所以我們就可以直接進行查詢操作來,這裡的時間複雜度為 o(1)。

例 2,我們來看第二個例子,如果是鍊錶,如何找到這個鍊錶中的第二個元素並輸出呢?

鍊錶和陣列一樣,都是 o(n) 空間複雜度的複雜資料結構。但其區別之一就是,陣列有 index 的索引,而鍊錶沒有。鍊錶是通過指標,讓元素按某個自定義的順序「手拉手」連線在一起的。

既然是這樣,要查詢其第二個元素,就必須要先知道第乙個元素在**。以此類推,鍊錶中某個位置的元素的查詢,只能通過從前往後的順序逐一去查詢。不難發現,鍊錶因為沒有索引,只能「乙個接乙個」地按照位置條件查詢,在這種情況下時間複雜度就是 o (n)。

例 3,我們再來看第三個例子,關於數值條件的查詢。

我們要查詢出,資料結構中數值等於 5 的元素是否存在。這次的查詢,無論是陣列還是鍊錶都束手無策了。唯一的方法,也只有按照順序乙個接乙個地去判斷元素數值是否滿足等於 5 的條件。很顯然,這樣的查詢方法時間複雜度是 o(n)。那麼有沒有時間複雜度更低的方式呢?答案當然是:有。

我們遇到過要查詢出陣列**現次數最多的元素的情況。我們採用的方法是,把陣列轉變為字典,以儲存元素及其出現次數的 k-v 對映關係。而在每次的迴圈中,都需要對當前遍歷的元素,去查詢它是否在字典**現過。這裡就是很實際的按照元素數值查詢的例子。如果借助字典的資料型別,這個例子的查詢問題,就可以在 o(1) 的時間複雜度內完成了。

例 4,我們再來看第四個例子,關於複雜資料結構中新增資料,這裡有兩個可能.

第乙個是在乙個複雜資料結構的最後,新增一條資料。

第二個是在乙個複雜資料結構的中間某個位置,新增一條資料。

這兩個可能性的區別在於,新增了資料之後,是否會導致原有資料結構中資料的位置順序改變。接下來,我們分別來舉例說明。

在複雜資料結構中,新增一條資料。假設是在資料結構的最後新增資料。此時新增一條資料後,對原資料沒有產生任何影響。因此,執行的步驟是:

首先,通過查詢操作找到資料結構中最後乙個資料的位置;

接著,在這個位置之後,通過新增操作,賦值或者插入一條新的資料即可。

如果是在資料結構中間的某個位置新增資料,則會對插入元素的位置之後的元素產生影響,導致資料的位置依次加 1 。例如,對於某個長度為 4 的陣列,在第二個元素之後插入乙個元素。則修改後的陣列中,原來的第

一、第二個元素的位置不發生變化,第三個元素是新插入的元素,第

四、第五個元素則是原來的第

三、第四個元素。

我們再來看看刪除。在複雜資料結構中刪除資料有兩個可能:

第乙個是在這個複雜資料結構的最後,刪除一條資料。

第二個是在這個複雜資料結構的中間某個位置,刪除一條資料。

這兩個可能性的區別在於,刪除了資料之後,是否會導致原有資料結構中資料的位置順序改變。由於刪除操作和新增操作高度類似,我們就不再舉詳細闡述了。

通過上述例子的學習之後,你就可以對它們進行組合,去玩轉更複雜的資料操作了,我們再來看乙個例子。

例 5,在某個複雜資料結構中,在第二個元素之後新增一條資料。隨後再刪除第 1 個滿足數值大於 6 的元素。我們來試著分析這個任務的資料操作過程。這裡有兩個步驟的操作:

第一步,在第二個元素之後新增一條資料。這裡包含了查詢和新增兩個操作,即查詢第二個元素的位置,並在資料結構中間新增一條資料。

第二步,刪除第 1 個滿足數值大於 6 的元素。這裡包含查詢和刪除兩個操作,即查詢出第 1 個數值大於 6 的元素的位置,並刪除這個位置的元素。

因此,總共需要完成的操作包括,按照位置的查詢、新增和按照資料數值的查詢、刪除。

經過我們的分析,資料處理的基本操作只有 3 個,分別是增、刪、查。其中,增和刪又可以細分為在資料結構中間的增和刪,以及在資料結構最後的增和刪。區別就在於原資料的位置是否發生改變。查詢又可以細分為按照位置條件的查詢和按照資料數值特徵的查詢。幾乎所有的資料處理,都是這些基本操作的組合和疊加。

資料結構 鍊錶基本操作(增 刪 查 改)

鍊錶是一種物理儲存結構上非連續 非順序的儲存結構,資料元素的邏輯順序是通過鍊錶中的指標鏈結次序實現的。根據鍊錶是否是單向的 是否帶頭結點 是否迴圈可以將量表分為 8 種不同的結構。但是這裡我們只研究最簡單的不帶頭結點的單向不迴圈鍊錶,以及最難的帶頭結點的雙向迴圈鍊錶。1.不帶頭的單向不迴圈鍊錶,結構...

資料結構 順序表 增刪查改

include include include include list.h 函式名 createlist 函式功能 建立線性表 引數 無 list createlist void return plist 函式名 destroylist 函式功能 銷毀線性表 函式返回值 無。void destro...

資料結構 鍊錶的增刪改查

關於鍊錶的增刪改查 pragma once include include includetypedef int datatype typedef struct slistnode slistnode 初始化 void slistinit slistnode ppfirst 銷毀 void slis...