??個人主頁:聆風吟
??系列專欄:圖解數(shù)據(jù)結構、算法模板
??少年有夢不應止于心動,更要付諸行動。
一. ??線性表
1.1 ??線性表的定義
線性表(linear list):線性表是一種數(shù)據(jù)結構,由n個具有相同數(shù)據(jù)類型的元素構成一個有限序列。線性表可以用數(shù)組、鏈表、棧等方式實現(xiàn),常見的線性表有數(shù)組、鏈表、棧、隊列等,也可以自定義實現(xiàn)。
這里需要強調一下幾點:
????首先它是一個序列。也就是說,元素之間是有順序的。線性表中的元素稱為結點,相鄰結點之間的關系稱為鄰接關系。除第一個結點無前驅和最后一個結點無后繼外,其他每個結點有且僅有一個前驅和一個后繼。圖解如下:
注意:
線性表元素個數(shù)n (n >= 0)
定義為線性表的長度,當n=0
時,稱為空表。
1.2 ??線性表的存儲結構
???? 線性表的存儲結構有順序存儲結構和鏈式存儲結構兩種。前者稱為順序表,后者稱為鏈表:???? 其中,線性表在邏輯上是線性結構,也就說是連續(xù)的一條直線。但是在物理結構上并不一定是連續(xù)的,線性表在物理上存儲時,通常以數(shù)組和鏈式結構的形式存儲。
???? 本文主要詳細講解線性表的順序存儲結構 —— 順序表。線性表的鏈式存儲將在下期講解,言歸正傳接下來讓我們開始今天的 “主菜" 學習。
二. ??順序表
2.1 ??順序表定義
???? 順序表(Sequential List):用一段物理地址連續(xù)的存儲單元依次存儲數(shù)據(jù)元素的線性結構。一般情況下采用數(shù)組存儲。在數(shù)組上完成數(shù)據(jù)的增刪查改。
2.2 ??順序表的分類
順序表一般可以分為:靜態(tài)順序表和動態(tài)順序表。
2.2.1 ??靜態(tài)順序表
???? 靜態(tài)順序表:指存儲空間是固定的并且在程序運行前就已經確定大小的順序表。它通常使用數(shù)組來實現(xiàn),即通過定義一個固定長度的數(shù)組來存儲數(shù)據(jù)元素。
靜態(tài)順序表的結構代碼:
//靜態(tài)順序表 —— 使用定長數(shù)組存儲元素(不實用)
#define MAXSIZE 7//存儲單元初始分配量
typedef int SLDataType;//SLDataType類型根據(jù)實際情況而定,這里是int
typedef struct SeqList
{
SLDataType data[MAXSIZE];//定長數(shù)組
int size;//有效數(shù)據(jù)的個數(shù)
}SeqList;
我們可以發(fā)現(xiàn)描述靜態(tài)順序表需要三個屬性:
- 存儲空間的起始位置:數(shù)組
data
,他的存儲位置就是存儲空間的存儲位置; - 線性表的最大存儲容量:數(shù)組長的
MAXSIZE
; - 線性表的當前位置:
size
。
靜態(tài)順序表的優(yōu)缺點:
- 由于靜態(tài)順序表大小是固定的,因此不支持動態(tài)插入和刪除,但可以通過重新分配空間的方式來增加或減少容量;
- 靜態(tài)順序表的優(yōu)點:訪問數(shù)據(jù)快速,由于是連續(xù)存儲,所以可以直接通過下標訪問元素,效率高;
- 靜態(tài)順序表的缺點:空間利用率低,因為必須預留足夠的空間,以防止溢出。
2.2.2 ??動態(tài)順序表
???? 動態(tài)順序表:通過動態(tài)分配內存空間,實現(xiàn)隨著數(shù)據(jù)量的增加而不斷擴容的效果。它的結構類似于一個數(shù)組,數(shù)據(jù)元素的存儲是連續(xù)的,支持隨機訪問和順序訪問。
動態(tài)順序表的結構代碼:
//動態(tài)順序表 —— 使用動態(tài)開辟的數(shù)組存儲
typedef int SLDataType;//SLDataType類型根據(jù)實際情況而定,這里是int
typedef struct SeqList
{
SLDataType* a;//指向動態(tài)開辟的數(shù)組
int size;//有效數(shù)據(jù)的個數(shù)
int capacity;//記錄容量的空間大小
}SL;
我們可以發(fā)現(xiàn)描述動態(tài)順序表也需要三個屬性:
- 存儲空間的起始位置:指針
a
,他里面存儲的地址就是存儲空間的地址; - 線性表當前最大存儲容量:
capacity
,可以通過動態(tài)分配的方式進行擴容; - 線性表的當前位置:
size
。
動態(tài)順序表的優(yōu)缺點:
- 動態(tài)順序表的優(yōu)點:可以使用指針動態(tài)地分配內存,具有高效的存儲和訪問效率;
- 動態(tài)順序表的缺點:在插入和刪除元素時需要移動大量的數(shù)據(jù),效率較低。
三. ??順序表的基本操作實現(xiàn)
????通過上面的學習我們已經初步了解靜態(tài)順序表和動態(tài)順序表,有同學估計要問了在日常生活中我們應該使用哪種呢?在這里作者推薦大家使用動態(tài)順序表。因為動態(tài)順序表可以使程序更加高效和靈活,可以根據(jù)實際數(shù)據(jù)量動態(tài)地調整表的大小,避免在創(chuàng)建靜態(tài)順序表時浪費內存空間或者當數(shù)據(jù)量超出靜態(tài)順序表容量時造成數(shù)據(jù)丟失或程序崩潰等問題。本文也將采用動態(tài)順序表結合圖文去實現(xiàn)順序表的基本操作。
3.1 ??動態(tài)順序表結構體構建
//動態(tài)順序表
#define SLCAPACITY 4
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向動態(tài)開辟的數(shù)組
int size;//有效數(shù)據(jù)的個數(shù)
int capacity;//記錄容量的空間大小
}SL;
代碼深剖:
- 結構體中
a
指向的數(shù)組類型是我們重定義的SLDataType,這樣當我們想創(chuàng)建其它類型的順序表時只需要對typedef
后面的類型進行需改即可; -
size
是用來計數(shù)的,統(tǒng)計當前順序表一共有多少個有效元素; -
capacity
是用來表示當前順序表的容量,當size==capacity
時說明當前順序表已經“裝滿了”,需要擴容; - 定義標識符
SLCAPACITY
,方便后文對順尋表進行初始化可以方便改變capacity
的初始值。
3.2 ??初始化順序表
//初始化順序表
void SLInit(SL* ps)
{
assert(ps);
//使用malloc開辟空間
ps->a = (SLDataType*)malloc(sizeof(SLDataType)*4);
//判斷空間是否開辟成功
if (NULL == ps->a)
{
perror("malloc failed");
exit(-1);
}
ps->size = 0;
ps->capacity = SLCAPACITY;
}
代碼深剖:
- 在這里我們需要使用
assert
對ps
進行一下斷言,以防傳入空指針(后文在出現(xiàn)就不多做敘述了)。 - 使用
malloc
開辟空間,一定要進行判斷是否開辟成功,如果不進行判斷直接使用可能會導致程序崩潰。
時間復雜度:
該程序沒有循環(huán),根據(jù)大O階的推導方法很容易得出:初始化順序表的時間復雜度為
O(1)
3.3 ??銷毀順序表
//銷毀順序表
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
代碼深剖:
????為什么在這里要銷毀順序表呢?因為我們在這里使用的動態(tài)順序表,a
是通過malloc
進行動態(tài)申請的空間,如果使用了malloc分配的內存空間后忘記釋放,會導致內存泄漏,浪費系統(tǒng)資源甚至導致程序崩潰。
時間復雜度:
該程序沒有循環(huán),根據(jù)大O階的推導方法很容易得出:銷毀順序表的時間復雜度為
O(1)
3.4 ??打印順序表
//打印順序表
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
代碼深剖:
打印順序表就是進行簡單的遍歷循環(huán),此處不多做敘述。
時間復雜度:
該程序有單層循環(huán),根據(jù)大O階的推導方法很容易得出:打印順序表的時間復雜度為
O(n)
3.4 ??擴容
????因為擴容在尾插、頭插以及在pos位置插入都需要使用,因此我們可以把擴容單獨封裝成一個函數(shù),可以降低代碼的的冗余。整體思路圖解:
//檢查容量是否夠,不夠進行擴容
void SLCheckCapacity(SL* ps)
{
assert(ps);
//滿了要擴容
if (ps->size == ps->capacity)
{
//使用realloc進行擴容
SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * 2 * (ps->capacity));
//檢查是否擴容成功
if (temp == NULL)
{
perror("realloc failed");
exit(-1);
}
ps->a = temp;
ps->capacity *= 2;
}
}
代碼深剖:
????realloc
是C語言中的一個函數(shù),用于重新分配已經分配的內存空間的大小。它的原型是:
//頭文件
#include<stdlib.h>
//原型
extern void *realloc(void *mem_address, unsigned int newsize)
其中,mem_address
是指向已分配內存的指針,newsize
是新的內存大小。如果內存分配失敗,將會會返回NULL
。
時間復雜度:
該程序沒有循環(huán),根據(jù)大O階的推導方法很容易得出:擴容的時間復雜度為
O(1)
3.5 ??尾插
????尾插時需要先判斷順序表是否滿了,滿了要先進行擴容才能繼續(xù)進行擴容。size
表示有效元素個數(shù),同時也是順序表中最后一個元素后一個位置的下標。成功插入后要對有效數(shù)據(jù)個數(shù)size
進行加1操作。整體思路圖解:
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
//檢查是否需要擴容
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
時間復雜度:
該程序沒有循環(huán),根據(jù)大O階的推導方法很容易得出:尾插的時間復雜度為
O(1)
3.6 ??尾刪
整體思路圖解:
//尾刪
void SLPopBack(SL* ps)
{
assert(ps);
//溫柔檢查
/*if (ps->size == 0)
return;*/
//暴力檢查
assert(ps->size > 0);
ps->size--;
}
代碼深剖:
????在代碼中我們提供兩種檢查順序表是否為空的辦法。第一種是比較溫柔的檢查,如果順序表為空直接返回,返回之后仍然可以進行其他操作。第二種是比較暴力的檢查方法,直接提示錯誤并打印出錯誤位置的行號。
時間復雜度:
該程序沒有循環(huán),根據(jù)大O階的推導方法很容易得出:尾刪的時間復雜度為
O(1)
3.7 ??頭插
整體思路圖解:
//頭插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
//檢查是否需要擴容
SLCheckCapacity(ps);
//挪動數(shù)據(jù)
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
時間復雜度:
該程序需要執(zhí)行單層循環(huán),根據(jù)大O階的推導方法很容易得出:頭插的時間復雜度為
O(n)
3.8 ??頭刪
整體思路圖解:
//頭刪
void SLPopFront(SL* ps)
{
assert(ps);
//判斷順序表是否為空
assert(ps->size > 0);
//挪動元素向前覆蓋
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
時間復雜度:
該程序需要執(zhí)行單層循環(huán),根據(jù)大O階的推導方法很容易得出:頭插的時間復雜度為
O(n)
3.9 ??在下標為pos位置插入x
整體思路圖解:
//在下標為pos的位置插入x
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
//檢查pos是否在有效范圍內
assert(pos >= 0 && pos <= ps->size);
//檢查是否需要擴容
SLCheckCapacity(ps);
//挪動數(shù)據(jù)
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
//插入元素
ps->a[pos] = x;
ps->size++;
}
時間復雜度:
該程序需要執(zhí)行單層循環(huán),根據(jù)大O階的推導方法很容易得出:pos位置插入的時間復雜度為
O(n)
3.10 ??刪除下標為pos位置的數(shù)據(jù)
整體思路圖解:
//刪除下標為pos位置的數(shù)據(jù)
void SLErase(SL* ps, int pos)
{
assert(ps);
//檢查pos是否在有效范圍內
assert(pos >= 0 && pos < ps->size);
//挪動元素向前覆蓋
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
時間復雜度:
該程序需要執(zhí)行單層循環(huán),根據(jù)大O階的推導方法很容易得出:pos位置刪除的時間復雜度為
O(n)
3.11 ??查找某個值的下標
//查找某個值的下標,沒找到返回-1
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
時間復雜度:
該程序需要執(zhí)行單層循環(huán),根據(jù)大O階的推導方法很容易得出:查找的時間復雜度為
O(n)
四. ??順序表的完整源代碼
4.1 ??SeqList.h 順序表的函數(shù)聲明
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//動態(tài)順序表
#define SLCAPACITY 4
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向動態(tài)開辟的數(shù)組
int size;//有效數(shù)據(jù)的個數(shù)
int capacity;//記錄容量的空間大小
}SL;
//管理數(shù)據(jù) —— 增刪查改
//初始化
void SLInit(SL* ps);
//銷毀順序表
void SLDestroy(SL* ps);
//打印順序表
void SLPrint(SL* ps);
//檢查容量是否夠,不夠進行擴容
void SLCheckCapacity(SL* ps);
//尾插尾刪
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
//頭插頭刪
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//查找某個值的下標,沒找到返回-1
int SLFind(SL* ps, SLDataType x);
//在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x);
//刪除pos位置的數(shù)據(jù)
void SLErase(SL* ps, int pos);
4.2 ??SeqList.c 順序表的函數(shù)定義
#include "SeqList.h"
//初始化順序表
void SLInit(SL* ps)
{
assert(ps);
//使用malloc開辟空間
ps->a = (SLDataType*)malloc(sizeof(SLDataType)*4);
//判斷空間是否開辟成功
if (NULL == ps->a)
{
perror("malloc failed");
exit(-1);
}
ps->size = 0;
ps->capacity = SLCAPACITY;
}
//銷毀順序表
void SLDestroy(SL* ps)
{
assert(ps);
//釋放動態(tài)開辟的空間
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
//打印順序表
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//檢查容量是否夠,不夠進行擴容
void SLCheckCapacity(SL* ps)
{
assert(ps);
//滿了要擴容
if (ps->size == ps->capacity)
{
//使用realloc進行擴容
SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * 2 * (ps->capacity));
//檢查是否擴容成功
if (temp == NULL)
{
perror("realloc failed");
exit(-1);
}
ps->a = temp;
ps->capacity *= 2;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
//檢查是否需要擴容
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//尾刪
void SLPopBack(SL* ps)
{
assert(ps);
//暴力檢查
assert(ps->size > 0);
ps->size--;
}
//頭插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
//檢查是否需要擴容
SLCheckCapacity(ps);
//挪動數(shù)據(jù)
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
//頭刪
void SLPopFront(SL* ps)
{
assert(ps);
//判斷順序表是否為空
assert(ps->size > 0);
//挪動元素向前覆蓋
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
//查找某個值的下標
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
//在下標為pos的位置插入x
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
//檢查pos是否在有效范圍內
assert(pos >= 0 && pos <= ps->size);
//檢查是否需要擴容
SLCheckCapacity(ps);
//挪動數(shù)據(jù)
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
//插入元素
ps->a[pos] = x;
ps->size++;
}
//刪除下標為pos位置的數(shù)據(jù)
void SLErase(SL* ps, int pos)
{
assert(ps);
//檢查pos是否在有效范圍內
assert(pos >= 0 && pos < ps->size);
//挪動元素向前覆蓋
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
4.3 ??test.c 順序表功能測試
????在這里作者只給出頭插頭刪這組測試樣例,因為只需要調用前面的函數(shù),所以就不給大家挨個測試了,下來之后大家可以自行嘗試,多敲多練大家一塊進步。
#include "SeqList.h"
//尾插尾刪檢測
void TestSeqList1()
{
SL s;//創(chuàng)建順序表
SLInit(&s);//初始化
//尾插
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPrint(&s);//打印
//尾刪
SLPopBack(&s);
SLPopBack(&s);
SLPrint(&s);//打印
//銷毀順序表
SLDestroy(&s);
}
int main()
{
TestSeqList1();
return 0;
}
??總結
本文主要講解:文章來源:http://www.zghlxwxcb.cn/news/detail-814217.html
- 線性表的定義:由n個具有相同數(shù)據(jù)類型的元素構成一個有限序列;
- 線性表的存儲結構:順序存儲結構、鏈式存儲結構;
- 順序表的定義:用一段物理地址連續(xù)的存儲單元依次存儲數(shù)據(jù)元素的線性結構;
- 順序表的分類:靜態(tài)順序表、動態(tài)順序表;
- 順序表的增刪查改的實現(xiàn)。
???? 今天的干貨分享到這里就結束啦!如果覺得文章還可以的話,希望能給個三連支持一下,聆風吟的主頁還有很多有趣的文章,歡迎小伙伴們前去點評,您的支持就是作者前進的最大動力!文章來源地址http://www.zghlxwxcb.cn/news/detail-814217.html
到了這里,關于【圖解數(shù)據(jù)結構】順序表實戰(zhàn)指南:手把手教你詳細實現(xiàn)(超詳細解析)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!