線性表概念
- 線性表是零個(gè)或多個(gè)數(shù)據(jù)元素的有限序列
- 在復(fù)雜的線性表中,一個(gè)數(shù)據(jù)元素可以由若干個(gè)數(shù)據(jù)項(xiàng)組成
學(xué)號(hào) | 姓名 | 性別 | 出生年月 |
---|---|---|---|
1 | 張三 | 男 | 1995.3 |
2 | 李四 | 女 | 1994.8 |
3 | 王五 | 女 | 1994.12 |
線性表(List)抽象
- 假設(shè)線性表的數(shù)據(jù)對(duì)象集合為 {a1,a2….an} ,每個(gè)元素類型為 DataType,除第一個(gè)元素 a1 外,每個(gè)元素有且只有一個(gè)直接前驅(qū)元素,除了最后一個(gè)元素 an 外,每一個(gè)元素都有一個(gè)后繼元素
- 基本操作
方法 | 描述 |
---|---|
union(L la,L lb) | 將線性表中 lb 存在,但 la 不存在的數(shù)據(jù)元素插入到 la循環(huán)lb中每個(gè)元素,判斷當(dāng)前元素是否存在la中,若不存在,插入到la |
InitList(*L) | 初始化操作,建立一個(gè)空的線性表 L |
ListEmpty(L) | 若線性表為空則返回true,否則false |
ClearList(*L) | 清空線性表 |
GetElem(L,i,*e) | 返回第 i 個(gè)位置元素 |
LocateElem(L,e) | 查找線性表中與e相等的元素,查找成功返回元素,失敗返回0 |
ListInsert(*L,i,e) | 在線性表中第i個(gè)位置插入新元素e |
ListDelete(L,i,e) | 刪除第i個(gè)位置的元素,返回e值 |
ListLength(L) | 返回線性表長(zhǎng)度 |
線性表:順序存儲(chǔ)
-
用一段地址連續(xù)的存儲(chǔ)單元依次存儲(chǔ)線性表的數(shù)據(jù)元素,構(gòu)成順序存儲(chǔ)需要三個(gè)屬性:
- 存儲(chǔ)空間位置
- 數(shù)組長(zhǎng)度(MaxSize)
- 當(dāng)前長(zhǎng)度(length)
-
地址計(jì)算方法:
- 用 0 作為第一個(gè)元素下標(biāo),第 i 個(gè)元素存儲(chǔ)在數(shù)組 i-1 位置
- 假設(shè)每個(gè)元素占用 c 個(gè)存儲(chǔ)單元,那么線性表中第 i+1 個(gè)數(shù)據(jù)元素和第 i 個(gè)數(shù)據(jù)元素存儲(chǔ)位置滿足下列的關(guān)系
- 第 i 個(gè)數(shù)據(jù)元素ai存儲(chǔ)位置可以由a1推算得出
- 通過該公式可以隨時(shí)計(jì)算出線性表中任意位置的地址,不管是第一個(gè)還是最后一個(gè),都是相同的,它的存取時(shí)間性能為O(1),通常把具有這一特點(diǎn)的存儲(chǔ)結(jié)構(gòu)稱為隨機(jī)存取結(jié)構(gòu)
插入和刪除
- 插入:
- 若插入位置不合理,拋出異常
- 如果線性表長(zhǎng)度等于數(shù)組長(zhǎng)度,拋出異常或動(dòng)態(tài)增加容量
- 從最后一個(gè)元素向前遍歷到第 i 個(gè)位置,分別將它們向后移動(dòng)一個(gè)位置
- 將要插入的元素填入 i 處
- 表長(zhǎng)加 1
- 刪除:
- 如果刪除位置不合理,拋出異常
- 取出刪除元素
- 從刪除元素開始遍歷到最后一個(gè)元素,將它們向前移動(dòng)一個(gè)位置
- 表長(zhǎng)減 1
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):快速的存取表中任意位置的數(shù)據(jù)
- 缺點(diǎn):插入和刪除操作需要移動(dòng)大量元素,難以確定存儲(chǔ)空間容量,造成存儲(chǔ)空間“碎片”
線性表:鏈?zhǔn)酱鎯?chǔ)
- 不同于順序存儲(chǔ),存儲(chǔ)單元可以是連續(xù)的,也可以是不連續(xù)的,鏈?zhǔn)浇Y(jié)構(gòu)中,每個(gè)結(jié)點(diǎn)(Node)除了要保存數(shù)據(jù)元素信息外,還要存儲(chǔ)它后繼元素的存儲(chǔ)地址(引用)
- 為了更方便的操作鏈表,會(huì)在單鏈表的第一個(gè)結(jié)點(diǎn)前附設(shè)一個(gè)結(jié)點(diǎn),成為頭結(jié)點(diǎn),可以存取線性表長(zhǎng)度等附加信息
- 假設(shè)p是指向線性表第 i 個(gè)元素的指針,則該結(jié)點(diǎn)第 a?? 數(shù)據(jù)域可以用 p->data 表示,p->next->data = a?+1 元素
單鏈表的讀取
- 不同于順序存儲(chǔ),我們不知道第 i 個(gè)元素到底在哪?所以必須從頭開始找
獲取第i個(gè)數(shù)據(jù)元素的算法思路:- 聲明一個(gè)結(jié)點(diǎn) p 指向鏈表第一個(gè)結(jié)點(diǎn),初始化 j 從 1 開始
- 當(dāng) j<i 時(shí),就遍歷鏈表,讓 p 的指針向后移動(dòng),不斷指向下一個(gè)結(jié)點(diǎn), j 累加 1
- 若到鏈表末尾 p 為空,則說明第 i 個(gè)元素不存在
- 否則查找成功,返回結(jié)點(diǎn) p 元素
- 該算法的時(shí)間復(fù)雜度取決于 i 的位置,當(dāng) i=1 時(shí),不需要遍歷,第一個(gè)就取出了,而當(dāng) i=n 時(shí)遍歷 n-1 次才可以,最壞的情況是 O(n)
單鏈表的插入
- 若要插入 s 結(jié)點(diǎn),要實(shí)現(xiàn)結(jié)點(diǎn) p 、p->next 和 s之間的邏輯變化,讓 p 的后繼結(jié)點(diǎn)為 s 結(jié)點(diǎn),s的后繼結(jié)點(diǎn)為之前 p->next
- 單鏈表插入第 i 個(gè)數(shù)據(jù)元素結(jié)點(diǎn)的思路:
- 聲明一結(jié)點(diǎn) p 指向鏈表第一個(gè)結(jié)點(diǎn),初始化 j 從 1 開始
- 當(dāng) j<i 時(shí),遍歷鏈表,讓 p 指針向后移動(dòng),不斷指向下一結(jié)點(diǎn),j 累加 1
- 若到鏈表末尾 p 為空,則說明第 i 個(gè)元素不存在
- 否則查找成功,在系統(tǒng)中生成一個(gè)空結(jié)點(diǎn) s
- 將數(shù)據(jù)元素 e 賦值給 s->data
- 修改邏輯結(jié)構(gòu) s->next = p->next; p->next = s;
- 返回成功
- 單鏈表刪除第 i 個(gè)數(shù)據(jù)元素結(jié)點(diǎn)的思路:
- 聲明一結(jié)點(diǎn) p 指向鏈表第一個(gè)結(jié)點(diǎn),初始化 j 從 1 開始
- 當(dāng) j<i 時(shí),遍歷鏈表,讓 p 指針向后移動(dòng),不斷指向下一結(jié)點(diǎn),j 累加 1
- 若到鏈表末尾 p 為空,則說明第 i 個(gè)元素不存在
- 否則查找成功,將要?jiǎng)h除的結(jié)點(diǎn) p->next 賦給 q
- 單鏈表的刪除標(biāo)準(zhǔn)語(yǔ)句 p->next = q->next
- 將 q 結(jié)點(diǎn)中的數(shù)據(jù)賦給 e 并返回,釋放 q 結(jié)點(diǎn)
單鏈表的整表刪除
- 聲明一個(gè)結(jié)點(diǎn) p 和 q
- 將第一個(gè)結(jié)點(diǎn)賦值給 p
- 循環(huán):將下一個(gè)結(jié)點(diǎn)賦給 q ,釋放 p ,將 q 賦值給 p
單鏈表與順序存儲(chǔ)結(jié)構(gòu)的優(yōu)缺點(diǎn)
存儲(chǔ)分配方式
- 順序存儲(chǔ)結(jié)構(gòu)用一組連續(xù)的存儲(chǔ)單元依次存儲(chǔ)數(shù)據(jù)元素
- 單鏈表采用鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),用一組任意存儲(chǔ)單元存放線性表元素
時(shí)間性能
- 查找:順序存儲(chǔ)結(jié)構(gòu) O(1)、單鏈表 O(n)
- 插入和刪除:
- 順序存儲(chǔ)結(jié)構(gòu)需要平均移動(dòng)表長(zhǎng)一半的元素,時(shí)間為 O(n)。單鏈表在找出某指針位置指針后,插入和刪除時(shí)間為 O(1),對(duì)于插入或刪除數(shù)據(jù)頻繁操作,單鏈表效率優(yōu)勢(shì)更明顯
- 如我們希望從第 i 個(gè)位置插入 10 個(gè)元素,對(duì)于順序存儲(chǔ)結(jié)構(gòu)意味著,每一次插入都需要移動(dòng) n-i 個(gè)元素,每次都是 O(n),而單鏈表,只需在第一次時(shí)找到第 i 個(gè)位置的指針,此時(shí)為 O(n),接下來知識(shí)簡(jiǎn)單的賦值移動(dòng)指針,時(shí)間復(fù)雜度為 O(1)
空間性能
- 順序存儲(chǔ)結(jié)構(gòu)需要預(yù)先分配存儲(chǔ)空間,分大了,浪費(fèi),小了易溢出或需要不斷動(dòng)態(tài)擴(kuò)展
- 單鏈表不需要分配存儲(chǔ)空間,元素個(gè)數(shù)不受限
合并線性表性能
- 要將兩個(gè)順序存儲(chǔ)結(jié)構(gòu)的線性表合成一個(gè)時(shí)至少需要復(fù)制一個(gè)線性表全部結(jié)點(diǎn),甚至是兩個(gè)線性表的全部結(jié)點(diǎn)復(fù)制到新的更大的存儲(chǔ)空間中,時(shí)間復(fù)雜度為 O(n)
- 要將兩個(gè)鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)的線性表合成一個(gè)只需要修改線性表 A 的尾結(jié)點(diǎn)指向線性表 B 頭結(jié)點(diǎn)即可,時(shí)間復(fù)雜度為 O(1)
循環(huán)鏈表
- 單鏈表只存儲(chǔ)了向后的指針,到了尾標(biāo)志就停止了,這樣,某一結(jié)點(diǎn)就無法找到它的前驅(qū)結(jié)點(diǎn)
- 將單鏈表中的最后結(jié)點(diǎn)的指針指向頭結(jié)點(diǎn),就形成循環(huán)鏈表
- 單鏈表原來是判斷 p->next 是否為空來判斷循環(huán)是否結(jié)束,而循環(huán)鏈表是判斷 p->next 不等于頭結(jié)點(diǎn),則循環(huán)未結(jié)束
循環(huán)鏈表優(yōu)點(diǎn)
- 有了頭結(jié)點(diǎn)時(shí),我們可以用 O(1)時(shí)間訪問第一個(gè)結(jié)點(diǎn),但要訪問最后一個(gè)結(jié)點(diǎn),卻需要O(n)時(shí)間,因?yàn)槲覀冃枰獙捂湵砣繏呙枰槐?/li>
- 而循環(huán)鏈表不用頭指針,而是使用尾指針 rear 來表示循環(huán)鏈表,則查找最后結(jié)點(diǎn)為 O(1),而開始結(jié)點(diǎn)其實(shí)就是 rear->next->next ,時(shí)間復(fù)雜度也為 O(1)
將兩個(gè)循環(huán)鏈表合成一個(gè)
- 要將兩個(gè)循環(huán)鏈表合成一個(gè),只需要讓循環(huán)鏈表 A 的尾指針指向循環(huán)鏈表 B 的頭結(jié)點(diǎn),循環(huán)鏈表 B 的尾指針指向循環(huán)鏈表 A 的頭結(jié)點(diǎn)即可
雙向鏈表
- 在單鏈表和循環(huán)鏈表中,查找上一個(gè)結(jié)點(diǎn)的時(shí)間復(fù)雜度為O(n),因?yàn)槊看味家獜念^開始遍歷查找
- 雙向鏈表是在單鏈表的每個(gè)結(jié)點(diǎn)中,在設(shè)置一個(gè)指向前驅(qū)結(jié)點(diǎn)的指針域,所以結(jié)點(diǎn)中既有指向上一個(gè)結(jié)點(diǎn),也有指向下一個(gè)結(jié)點(diǎn)
- 雙向鏈表的插入與刪除比單鏈表復(fù)雜點(diǎn),例如插入 s 結(jié)點(diǎn)的順序:
- 把 p 賦值給 s 的前驅(qū)
- 把 p->next 賦值給 s 的后繼
- 把 s 賦值給 p->next 的前驅(qū)
- 把 s 賦值給 p 的后繼