目錄
線性表
順序表
順序表的實作
順序表的初始化
順序表的銷毀
順序表的列印
順序表檢查是否要擴容
順序表的尾插
順序表的頭插
順序表的尾刪
順序表的頭刪
順序表的查找
順序表在pos位置插入x
順序表洗掉pos位置的值
順序表修改pos位置的值
順序表的缺陷
力扣練習題
Leetcode27.移除元素
Leetcode26.洗掉有序陣列中的重復項
Leetcode88.合并兩個有序陣列
Leetcode189.旋轉陣列
線性表
線性表(linear list)是n個具有相同特性的資料元素的有限序列,線性表是一種在實際中廣泛使用的資料結構,常見的線性表:順序表、鏈表、堆疊、佇列、字串等等
線性表在邏輯上是線性結構的,也就說是連續的一條直線,但是在物理結構上并不一定是連續的,線性表在物理上存盤時,通常以陣列和鏈式結構的形式存盤,

順序表
順序表使用一段物理地址連續的存盤單元依次存盤資料元素的線性結構,一般情況下采用資料存盤,在陣列上完成資料的增刪查改,
順序表一般分為:
1.靜態順序表:使用定長陣列存盤元素

2.動態順序表:使用動態開辟的陣列存盤

了解了線性表與順序表之后,下面我們就來實作以下動態順序表吧,
順序表的實作
函式介面的宣告
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
//動態順序表
typedef struct Seqlist
{
SLDataType *a;
int sz;//表示陣列中存盤了多少個資料
int capacity;//表示陣列實際能存盤的空間容量
}SL;
//初始化
void SeqlistInit(SL* ps);
//順序表的列印
void SeqlistPrint(SL* ps);
//順序表的銷毀
void SeqlistDestory(SL* ps);
//尾插
void SeqlistPushBack(SL*ps, SLDataType x);
//頭插
void SeqlistPushFront(SL*ps, SLDataType x);
//尾刪
void SeqlistPopBack(SL*ps);
//頭刪
void SeqlistPopFront(SL*ps);
//順序表查找
int SeqlistFind(SL*ps, SLDataType x);
//順序表檢查是否要擴容
void SeqlistCheck(SL*ps);
//順序表在pos位置插入x
void SeqlisInsert(SL*ps, int pos, SLDataType x);
//順序表洗掉pos位置的值
void SeqlistErase(SL*ps, int pos);
//順序表修改pos位置的值
void SeqlistModify(SL*ps, int pos, SLDataType x);
函式介面的實作:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
//順序表檢查是否要擴容
void SeqlistCheck(SL*ps)
{
if (ps->sz == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType*)*newcapacity);
//當一個指標為NULL指標時,realloc就相當于malloc,這里我們還需要判斷一下是否擴容成功
if (temp == NULL)
{
printf("realloc failed\n");
exit(-1);
}
//擴容成功
else
{
ps->a = temp;
ps->capacity = newcapacity;
}
}
}
//初始化的實作
void SeqlistInit(SL* ps)
{
ps->a = NULL;
ps->sz = ps->capacity = 0;
}
//順序表的列印
void SeqlistPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->sz; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//順序表的銷毀
void SeqlistDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->a = 0;
ps->capacity = 0;
}
//尾插的實作
void SeqlistPushBack(SL*ps, SLDataType x)
{
assert(ps);
//如果空間不夠或者空間為0時,就得擴容
SeqlistCheck(ps);
//if (ps->sz == ps->capacity)
//{
// int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType*)*newcapacity);
// //當一個指標為NULL指標時,realloc就相當于malloc,這里我們還需要判斷一下是否擴容成功
// if (temp == NULL)
// {
// printf("realloc failed\n");
// exit(-1);
// }
// //擴容成功
// else
// {
// ps->a = temp;
// ps->capacity = newcapacity;
// }
//}
//假如空間足夠,那么我們直接往后插入
ps->a[ps->sz] = x;
ps->sz++;//順序表有效元素個數+1
}
//頭插的實作
void SeqlistPushFront(SL*ps, SLDataType x)
{
assert(ps);
//當空間不夠的時候就擴容
SeqlistCheck(ps);
//if (ps->sz == ps->capacity)
//{
// int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType*)*newcapacity);
// //當一個指標為NULL指標時,realloc就相當于malloc,這里我們還需要判斷一下是否擴容成功
// if (temp == NULL)
// {
// printf("realloc failed\n");
// exit(-1);
// }
// //擴容成功
// else
// {
// ps->a = temp;
// ps->capacity = newcapacity;
// }
//}
//當空間足夠的時候,頭插的話得向后挪并且是從后往前挪,從前往后會造成覆寫達不到效果
int end = ps->sz-1;
while (end>=0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->sz++;//順序表有效元素個數+1
}
//尾刪的實作
void SeqlistPopBack(SL*ps)
{
assert(ps);
assert(ps->sz > 0);
ps->sz--;//順序表有效元素個數-1
}
//頭刪的實作
void SeqlistPopFront(SL*ps)
{
assert(ps);
assert(ps->sz > 0);
int begin = 1;
while (begin <ps->sz)
{
//從前往后覆寫
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->sz--;//順序表有效元素個數-1
}
//順序表查找
int SeqlistFind(SL*ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->sz; i++)
{
if (ps->a[i] == x)
{
//找到了
return i;
}
}
//找不到
return -1;
}
//順序表在pos位置插入x
void SeqlisInsert(SL*ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->sz);
//當空間不夠的時候就擴容
SeqlistCheck(ps);
//相當于頭插
int end = ps->sz - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->sz++;//順序表有效元素個數+1
}
//順序表洗掉pos位置的值
void SeqlistErase(SL*ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->sz);
int begin = pos;
while (begin < ps->sz)
{
//從前往后覆寫
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->sz--;
}
//順序表修改pos位置的值
void SeqlistModify(SL*ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->sz);
ps->a[pos] = x;
}
順序表的初始化
將結構體中指向動態開辟的陣列的指標置空,將size與capacity置為0.
//初始化的實作
void SeqlistInit(SL* ps)
{
ps->a = NULL;
ps->sz = ps->capacity = 0;
}
順序表的銷毀
釋放在堆上動態開辟的空間,并且將size與capacity置成0
//順序表的銷毀
void SeqlistDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->a = 0;
ps->capacity = 0;
}
順序表的列印
依次列印動態開辟的陣列中的有效資料
//順序表的列印
void SeqlistPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->sz; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
順序表檢查是否要擴容
當size與capacity相等并且都為0時,就將capacity的容量變成4,若是size與capacity相等但是不為0時,我們就需要擴容了將capacity的容量擴成2倍,
//順序表檢查是否要擴容
void SeqlistCheck(SL*ps)
{
if (ps->sz == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType*)*newcapacity);
//當一個指標為NULL指標時,realloc就相當于malloc,這里我們還需要判斷一下是否擴容成功
if (temp == NULL)
{
printf("realloc failed\n");
exit(-1);
}
//擴容成功
else
{
ps->a = temp;
ps->capacity = newcapacity;
}
}
}
順序表的尾插
首先要判斷一下空間容量是否充足,若空間滿了就應該當先擴容再尾插,若空間充足即可直接尾插,
//尾插的實作
void SeqlistPushBack(SL*ps, SLDataType x)
{
assert(ps);
//如果空間不夠或者空間為0時,就得擴容
SeqlistCheck(ps);
//if (ps->sz == ps->capacity)
//{
// int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType*)*newcapacity);
// //當一個指標為NULL指標時,realloc就相當于malloc,這里我們還需要判斷一下是否擴容成功
// if (temp == NULL)
// {
// printf("realloc failed\n");
// exit(-1);
// }
// //擴容成功
// else
// {
// ps->a = temp;
// ps->capacity = newcapacity;
// }
//}
//假如空間足夠,那么我們直接往后插入
ps->a[ps->sz] = x;
ps->sz++;//順序表有效元素個數+1
}
順序表的頭插
首先要判斷一下空間容量是否充足,若空間滿了應當先擴容,若空間足夠想要頭插的話得將有效資料先往后挪并且是從后往前挪,從前往后會造成覆寫達不到想要的效果,挪動之后再插入資料,
//頭插的實作
void SeqlistPushFront(SL*ps, SLDataType x)
{
assert(ps);
//當空間不夠的時候就擴容
SeqlistCheck(ps);
//if (ps->sz == ps->capacity)
//{
// int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// SLDataType* temp = (SLDataType*)realloc(ps->a, sizeof(SLDataType*)*newcapacity);
// //當一個指標為NULL指標時,realloc就相當于malloc,這里我們還需要判斷一下是否擴容成功
// if (temp == NULL)
// {
// printf("realloc failed\n");
// exit(-1);
// }
// //擴容成功
// else
// {
// ps->a = temp;
// ps->capacity = newcapacity;
// }
//}
//當空間足夠的時候,頭插的話得向后挪并且是從后往前挪,從前往后會造成覆寫達不到效果
int end = ps->sz-1;
while (end>=0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->sz++;//順序表有效元素個數+1
}
順序表的尾刪
首先對有效資料sz進行判斷,如果有效資料為0就不需要洗掉,大于0就只需要將ps->sz--就可以了,
//尾刪的實作
void SeqlistPopBack(SL*ps)
{
assert(ps);
assert(ps->sz > 0);
ps->sz--;//順序表有效元素個數-1
}
順序表的頭刪
首先對有效資料sz進行判斷,如果有效資料為0就不需要洗掉,如果有效資料大于0的話,需要將有效資料向前挪動,并且是進行從前往后的覆寫,然后再將ps->sz--就可以達到效果了
//頭刪的實作
void SeqlistPopFront(SL*ps)
{
assert(ps);
assert(ps->sz > 0);
int begin = 1;
while (begin <ps->sz)
{
//從前往后覆寫
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->sz--;//順序表有效元素個數-1
}
順序表的查找
在有效資料中進行查找,找到了就回傳該位置的下標,找不到則回傳-1;
//順序表查找
int SeqlistFind(SL*ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->sz; i++)
{
if (ps->a[i] == x)
{
//找到了
return i;
}
}
//找不到
return -1;
}
順序表在pos位置插入x
要想插入資料,首先就得檢查空間容量是否足夠,空間不足的話首先的擴容,空間足夠的話需要將pos位置與pos后面的資料向后挪動,并且是從后往前挪,再將x插入到pos的位置上,(需要注意的是pos的位置必須>=0并且不能超過ps->sz-1),最后對ps->sz++即可
//順序表在pos位置插入x
void SeqlisInsert(SL*ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->sz);
//當空間不夠的時候就擴容
SeqlistCheck(ps);
//相當于頭插
int end = ps->sz - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->sz++;//順序表有效元素個數+1
}
順序表洗掉pos位置的值
洗掉pos位置的值,需要將pos位置之后的元素從前往后挪動,覆寫pos位置的資料,最后再將ps->sz--即可
//順序表洗掉pos位置的值
void SeqlistErase(SL*ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->sz);
int begin = pos;
while (begin < ps->sz)
{
//從前往后覆寫
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->sz--;
}
順序表修改pos位置的值
直接修改pos位置的值即可
//順序表修改pos位置的值
void SeqlistModify(SL*ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->sz);
ps->a[pos] = x;
}
順序表的缺陷
1.空間不夠了需要擴容,增容是需要付出代價的
2.避免頻繁擴容(頻繁擴容會導致記憶體碎片變多),空間滿了我們一般都是擴2倍,這樣做可能會導致一定的空間浪費
3.順序表要求資料從開始位置連續存盤,那么我們在頭部或者中間位置插入洗掉資料就需要挪動資料,效率不高
力扣練習題
Leetcode27.移除元素
27. 移除元素題目鏈接:27. 移除元素
題目描述:
給你一個陣列 nums 和一個值 val,你需要 原地 移除所有數值等于 val 的元素,并回傳移除后陣列的新長度,
不要使用額外的陣列空間,你必須僅使用 O(1) 額外空間并 原地 修改輸入陣列,
元素的順序可以改變,你不需要考慮陣列中超出新長度后面的元素,
思路一:遍歷陣列找到等于val的值,通過挪動資料來覆寫val達到移除所有等于val的元素的效果,時間復雜度是O(N^2),空間復雜度O(1)
思路二:開一個與原陣列空間一樣大的陣列,然后再遍歷原陣列,不等于val的值就放到新陣列里面去,然后再將新陣列的值拷回到原陣列,時間復雜度是O(N),空間復雜度O(N),這是一種拿空間換時間的做法,
思路三:雙指標的做法,定義一個src與dest,并且讓它倆都從陣列0下標開始,如果src下標的值等于val,直接讓src++,dest不動,如果src下標的值不等于val,那么就把src下標的值賦給dest下標的值,并且讓src++,dest++,最后回傳的dest正好就是陣列新的長度,時間復雜度O(N),空間復雜度O(1)
int removeElement(int* nums, int numsSize, int val){ int src = 0; int dest = 0; //遍歷完這個陣列就結束 while(src<numsSize) { //當src下標元素等于val時,src++ if(nums[src]==val) { src++; } //否則的話將src下標元素的值賦給dest下標元素的值,然后src++,dest++ else { nums[dest] = nums[src]; dest++; src++; } } return dest; }
Leetcode26.洗掉有序陣列中的重復項
題目鏈接:26. 洗掉有序陣列中的重復項
題目描述:
給你一個有序陣列 nums ,請你 原地 洗掉重復出現的元素,使每個元素 只出現一次 ,回傳洗掉后陣列的新長度,
不要使用額外的陣列空間,你必須在 原地 修改輸入陣列 并在使用 O(1) 額外空間的條件下完成,
思路:這道題我們還是采用雙指標的解法,不過是快慢指標,定義兩個變數,一個是快指標src=1,一個是慢指標dest=0,先考慮特殊情況(如果陣列大小為0,直接回傳0即可) 然后再來看一般情況,通過src去遍歷陣列,如果src下標元素的值與dest下標元素的值相等,那么讓src++,dest不動,如果不相等,就先讓dest++再將src下標元素的值賦給dest下標元素的值,再讓src++即可,時間復雜度O(N),空間復雜度O(1).
int removeDuplicates(int* nums, int numsSize){ int src = 1;//快指標 int dest = 0;//慢指標 //特殊情況陣列大小為0 if(numsSize==0) { return 0; } //遍歷陣列 while(src<numsSize) { //如果快指標和慢指標所指向的元素是一樣的,那么慢指標不動,快指標向后移 if(nums[src]==nums[dest]) { src++; } //如果快指標和慢指標所指向的元素不一樣的,那么慢指標先向后移,將快指標指向元素的值賦給慢指標,快指標再往后移 else { dest++; nums[dest] = nums[src]; src++; } } return dest+1; }
Leetcode88.合并兩個有序陣列
題目鏈接:88. 合并兩個有序陣列
題目描述:
給你兩個按 非遞減順序 排列的整數陣列 nums1 和 nums2,另有兩個整數 m 和 n ,分別表示 nums1 和 nums2 中的元素數目,
請你 合并 nums2 到 nums1 中,使合并后的陣列同樣按 非遞減順序 排列,
注意:最終,合并后陣列不應由函式回傳,而是存盤在陣列 nums1 中,為了應對這種情況,nums1 的初始長度為 m + n,其中前 m 個元素表示應合并的元素,后 n 個元素為 0 ,應忽略,nums2 的長度為 n ,
思路:這道題我們需要采用歸并的思想,并且是從后往前歸并(從大到小),從前往后的話會造成覆寫達不到我們想要的效果,由于nums1的長度是nums1與nums2有效資料長度之和,因此從nums1與nums2中挑選出較大的數依次插入到nums1陣列的后面(從后往前),如果nums2先插入完了,那么我們直接回傳nums1就可以了,但是如果是nums1的有效資料先插入完,但是nums2的優先資料還沒插入完的話,我們還需要額外將nums2剩余的資料插入到nums1中,時間復雜度O(N),空間復雜度O(1).
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){ int end1 = m-1; int end2 = n-1; int end = n+m-1;//num1最后一個元素的下標 while(end1>=0&&end2>=0) { if(nums1[end1]>nums2[end2]) { nums1[end] = nums1[end1]; end--; end1--; } else { nums1[end] = nums2[end2]; end--; end2--; } } //nums1的有效數已經全放到了nums1后面的位置,nums2還沒有完全放進去 while(end2>=0) { nums1[end] = nums2[end2]; end--; end2--; } return nums1; }
Leetcode189.旋轉陣列
題目鏈接:189. 旋轉陣列
題目描述:
給定一個陣列,將陣列中的元素向右移動 k 個位置,其中 k 是非負數,
思路:先將n-k個數進行逆置,再將后k個數進行逆置,最后再整體逆置,這樣就達到我們想要的效果了,時間復雜度O(N),空間復雜度O(1).
void Reverse(int*nums,int left,int right) { while(left<right) { int temp = nums[left]; nums[left] = nums[right]; nums[right] = temp; left++; right--; } } void rotate(int* nums, int numsSize, int k){ if(k>=numsSize) { k%=numsSize; } //前n-k個數逆置 Reverse(nums,0,numsSize-k-1); //后k個數逆置 Reverse(nums,numsSize-k,numsSize-1); //整體逆置 Reverse(nums,0,numsSize-1); }
以上就是本篇文章的全部內容了,大家學資料結構的時候不能夠光看視頻,一定要下去多敲代碼做題目鞏固自己所學的知識,如果覺得博主的文章對你有幫助的話,可以點贊評論收藏支持一下博主,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/342040.html
標籤:其他





