1.瞭解順序表
1.1 首先,我們先要了解一下順序表是什麼?
順序表是在計算機內存中以數組的形式保存的線性表,線性表的順序存儲是指用一組地址連續的存儲單元依次存儲線性表中的各個元素、使得線性表中在邏輯結構上相鄰的數據元素存儲在相鄰的物理存儲單元中,即通過數據元素物理存儲的相鄰關係來反映數據元素之間邏輯上的相鄰關係,採用順序存儲結構的線性表通常稱爲順序表。順序表是將表中的結點依次存放在計算機內存中一組地址連續的存儲單元中。
各位小夥伴是不是看到這一段話彷彿被當頭一棍,感覺整個人都不好了。
簡單來說的話,就是將數組中的各個元素按順序依次存放在順序表的結點中。然後順序表會將這些結點存放在內存中的一塊地址連續的空間中。
就像下面圖片表示的那樣
1.2 其次我們清楚順序表的存儲結構是什麼?
順序表的存儲結構指的是一段地址連續的存儲單元依次存儲順序表的數據元素
描述順序表存儲結構需要三個元素:
- 存儲空間的起始位置:數組datas的存儲位置
- 順序表的最大存儲容量:數組長度,MAX_SIZE
- 順序表的當前長度
順序表的存儲結構:
//1.需要定義順序表的最大存儲空間
#define MAX_SIZE
//2.需要有統一類型的元素集合
typedef int ElemType;
typedef struct{
int id,
char * name;
}ElemenType; //爲元素集合起一個Element的別名
//3.定義結構
typedef struct{
ElementType datas[MAX_SIZE]; //datas的最大容量爲MAX_SIZE
int length; //順序表的當前長度
}SeqList; //結構的別名SeqList
1.3 最後我們再來看看計算機中的地址是如何計算的?
在計算機中:
- 第n 個元素的內存地址 = 數組的內存地址 + 第n 個元素的下標
- 這裏還要解釋一些兩個名詞position 和index
- position:位置,從1開始(就是下面圖中的順序存儲數據元素);
- index:下標,從0開始(圖中的下標)。
2.在順序表中實現插入和刪除
在瞭解了順序表的一些基礎知識之後,我們開始學習兩個算法(插入算法和刪除算法)
2.1 插入算法:
2.1.1 還是老樣子,我們先來了解插入算法的原理是什麼?
- 將數據元素a 插入到順序表(a_1, a_2, ····, a_n)因爲打不出來下標,所以用下劃線代替!下標爲i 的位置
- 下標爲i 及下標爲i 以後的所有數據元素後移;
- 下標i 的位置放入數據元素a。
2.1.2 注意:
- 插入元素後的順序表長度要爲n+1;
- 插入元素後,最後一個元素的下標變爲n;
- C 語言數組實現時,順序表長度不能超過它的最大長度;
- 除非i = n + 1,否則必須移動數據元素的位置來適應邏輯結構的改變。
2.1.3 代碼實現
首行註釋爲文件名,需要創建4個文件,默認的main.c、DataElement.h(用來定義數據元素)、SequenceList.h(定義函數)、SequenceList.c(實現函數)
//main.c
#include <stdio.h>
#include <stdlib.h>
#include "DataElement.h"
//測試數據
ElementType dataArray[] = {
{1, "巴菲特"},
{2, "比爾·蓋茨"},
{3, "喬布斯"}
};
//測試函數
void TestSequenceList();
int main()
{
TestSequenceList(); //調用測試函數
return 0;
}
void TestSequenceList()
{
SeqList seqList; //要操作的順序表
InitList(&seqList, dataArray, sizeof(dataArray) / sizeof(dataArray[0])); //初始化順序表
//sizeof(dataArray) / sizeof(dataArray[0]) 用sizeof 來求出dataArray 數組的整個長度,再除以單個元素的長度,就得出了數組元素的個數。
PrintfList(&seqList); //打印順序表
}
注意:
因爲PrintList()方法的聲明的第一個參數是SeqList * 類型,所以在調用PrintList()方法時,第一個參數應該爲SeqList * 結構體指針類型,因此傳入參數爲&seqList。
//DataElement.h
#ifndef DATAELEMENT_H_INCLUDED
#define DATAELEMENT_H_INCLUDED
//1.定義順序表的最大空間
#define MAX_SIZE 255
//2.定義數據類型
//typedef int ElementType;
/*
* datas = {{1, ""}, {2, ""}};
*/
typedef struct{
int id;
char * name;
}ElementType;
//3.定義順序表的結構
typedef struct{
ElementType datas[MAX_SIZE]; //表明順序表的最大空間
int length; //記錄順序表的長度
}SeqList;
#endif // DATAELEMENT_H_INCLUDED
//SequenceList.h
#ifndef SEQUENCELIST_H_INCLUDED
#define SEQUENCELIST_H_INCLUDED
//要引用的頭文件
#include <stdio.h>
#include <stdlib.h>
#include "DataElement.h"
/**
* 初始化順序表
* @param seqList 要初始化的順序表
* @param elemArray 初始化時添加的元素內容數組
* @param length 初始化時添加的元素個數
*/
void InitList(SeqList * seqList, ElementType * elemArray, int length);
/**
* 向順序表中的index下標處插入某個元素
* @param seqList 要初始化的順序表
* @param index 要插入的下標位置
* @param element 要插入的元素
*/
void InserElement(SeqList * seqList, int index, ElementType element);
/**
* 打印seqList
*/
void PrintfList(SeqList * seqList);
#endif // SEQUENCELIST_H_INCLUDED
//SequenceList.c
#ifndef SEQUENCELIST_C_INCLUDED
#define SEQUENCELIST_C_INCLUDED
//引入頭文件
#include "SequenceList.h"
/**
* 初始化順序表
* @param seqList 要初始化的順序表
* @param elemArray 初始化時添加的元素內容數組
* @param length 初始化時添加的元素個數
*/
void InitList(SeqList * seqList, ElementType * elemArray, int length)
{
if(length > MAX_SIZE)
{
printf("超出數組的最大容量,初始化失敗!\n");
return;
}
seqList -> length = 0; //記得在初始化順序表的時候,將順序表的長度置零
for(int i = 0; i < length; i++)
{
//每次循環在下標爲i 的位置插入一個元素
InserElement(seqList, i, elemArray[i]);
}
}
/**
* 向順序表中的index下標處插入某個元素
* @param seqList 要初始化的順序表
* @param index 要插入的下標位置
* @param element 要插入的元素
*/
void InserElement(SeqList * seqList, int index, ElementType element)
{
//1.驗證插入後的空間元素是否超過MAX_SIZE;
//2.index 的值是否合法;
//3.插入的index 應該在length 之內;
//4.從第length - 1 個下標開始,前面一個元素賦值給後面一個元素
if(seqList -> length + 1 >= MAX_SIZE) //插入後的順序表長度length 不能超過總長度MAX_SIZE,所以要把原來的length + 1 進行判斷。
{
printf("要插入的數組空間已滿!無法插入!\n");
return; //在輸入不合法的時候,一定要記得用return 來退出程序。
}
if(index < 0 || index > MAX_SIZE - 1) //下標數最小爲0,最大爲MAX_SIZE - 1。因爲下標比長度要-1。
{
printf("只能在允許的下標範圍內插入元素[0, %d]\n", MAX_SIZE - 1);
return;
}
if(index > seqList -> length) //下標不應該大於順序表的元素個數
{
printf("插入的下標超過了數組的最大長度1,插入失敗!\n");
return;
}
//在c89 標準中,直接在for 循環中定義變量是不允許的;
//在c99 標準以後就允許。
for(int i = seqList -> length - 1; i >= index; i--)
{
seqList -> datas[i + 1] = seqList -> datas[i];
}
//5.將要插入的值賦值給第index 個元素。
seqList -> datas[index] = element;
//6.順序表長度length + 1!!!- 一定要注意不要遺漏這條語句!
seqList -> length++;
}
/**
* 打印seqList
*/
void PrintfList(SeqList * seqList)
{
for(int i = 0; i < seqList -> length; i++)
{
printf("%d\t%s\n", seqList -> datas[i], seqList -> datas[i].name);
}
}
#endif // SEQUENCELIST_C_INCLUDED
2.2 既然有插入,那麼自然也有刪除算法,禮尚往來嘛~
2.2.1 刪除算法的原理
- 將第i 個數據元素從順序表(a_1,a_2,···,a_n)中刪除;
- 找到第i 個位置的數據元素;
- 將第i 個數據元素以後的數據元素依次覆蓋前一個位置;
- 順序表當前長度減1。
2.2.2 代碼實例
首行註釋爲文件名,需要創建4個文件,默認的main.c、DataElement.h(用來定義數據元素)、SequenceList.h(定義函數)、SequenceList.c(實現函數)
//main.c
#include <stdio.h>
#include <stdlib.h>
#include "DataElement.h"
//測試數據
ElementType dataArray[] = {
{1, "巴菲特"},
{2, "比爾·蓋茨"},
{3, "喬布斯"}
};
//測試函數
void TestSequenceList();
int main()
{
TestSequenceList(); //調用測試函數
return 0;
}
void TestSequenceList()
{
SeqList seqList; //要操作的順序表
ElementType *delElement; //保存刪除的元素
InitList(&seqList, dataArray, sizeof(dataArray) / sizeof(dataArray[0])); //初始化順序表
//sizeof(dataArray) / sizeof(dataArray[0]) 用sizeof 來求出dataArray 數組的整個長度,再除以單個元素的長度,就得出了數組元素的個數。
printf("初始化後\n");
PrintfList(&seqList); //打印順序表
delElement = DeleteElement(&seqList,2);
printf("刪除後\n");
PrintfList(&seqList);
printf("被刪除的元素:\n");
printf("%d\t%s\n", delElement -> id, delElement -> name);
free(delElement); //釋放分配的內存。
}
//DataElement.h
#ifndef DATAELEMENT_H_INCLUDED
#define DATAELEMENT_H_INCLUDED
//1.定義順序表的最大空間
#define MAX_SIZE 255
//2.定義數據類型
//typedef int ElementType;
/*
* datas = {{1, ""}, {2, ""}};
*/
typedef struct{
int id;
char * name;
}ElementType;
//3.定義順序表的結構
typedef struct{
ElementType datas[MAX_SIZE]; //表明順序表的最大空間
int length; //記錄順序表的長度
}SeqList;
#endif // DATAELEMENT_H_INCLUDED
//SequenceList.h
#ifndef SEQUENCELIST_H_INCLUDED
#define SEQUENCELIST_H_INCLUDED
//要引用的頭文件
#include <stdio.h>
#include <stdlib.h>
#include "DataElement.h"
//定義TURE 和FALSE 的常量
#define TRUE 1
#define FALSE 0
/**
* 初始化順序表
* @param seqList 要初始化的順序表
* @param elemArray 初始化時添加的元素內容數組
* @param length 初始化時添加的元素個數
*/
void InitList(SeqList * seqList, ElementType * elemArray, int length);
/**
* 向順序表中的index下標處插入某個元素
* @param seqList 要初始化的順序表
* @param index 要插入的下標位置
* @param element 要插入的元素
*/
void InserElement(SeqList * seqList, int index, ElementType element);
/**
* 刪除順序表中指定下標的元素
* @param seqList 要操作的順序表
* @param index 要刪除的下標
* @return 返回刪除的元素,如果刪除失敗,則返回NULL
*/
ElementType * DeleteElement(SeqList * seqList, int index);
/**
* 返回順序表中指定下標的元素
* @param seqList 要操作的順序表
* @param index 要返回元素的下標
* @return 返回指定下標的元素,如果查找失敗,則返回NULL
*/
ElementType * GetElement(SeqList * seqList, int index);
/**
* 打印seqList
*/
void PrintfList(SeqList * seqList);
#endif // SEQUENCELIST_H_INCLUDED
//SequenceList.c
#ifndef SEQUENCELIST_C_INCLUDED
#define SEQUENCELIST_C_INCLUDED
//引入頭文件
#include "SequenceList.h"
/**
* 初始化順序表
* @param seqList 要初始化的順序表
* @param elemArray 初始化時添加的元素內容數組
* @param length 初始化時添加的元素個數
*/
void InitList(SeqList * seqList, ElementType * elemArray, int length)
{
if(length > MAX_SIZE)
{
printf("超出數組的最大容量,初始化失敗!\n");
return;
}
seqList -> length = 0; //記得在初始化順序表的時候,將順序表的長度置零
for(int i = 0; i < length; i++)
{
//每次循環在下標爲i 的位置插入一個元素
InserElement(seqList, i, elemArray[i]);
}
}
/**
* 向順序表中的index下標處插入某個元素
* @param seqList 要初始化的順序表
* @param index 要插入的下標位置
* @param element 要插入的元素
*/
void InserElement(SeqList * seqList, int index, ElementType element)
{
//1.驗證插入後的空間元素是否超過MAX_SIZE;
//2.index 的值是否合法;
//3.插入的index 應該在length 之內;
//4.從第length - 1 個下標開始,前面一個元素賦值給後面一個元素
if(seqList -> length + 1 >= MAX_SIZE) //插入後的順序表長度length 不能超過總長度MAX_SIZE,所以要把原來的length + 1 進行判斷。
{
printf("要插入的數組空間已滿!無法插入!\n");
return; //在輸入不合法的時候,一定要記得用return 來退出程序。
}
if(index < 0 || index > MAX_SIZE - 1) //下標數最小爲0,最大爲MAX_SIZE - 1。因爲下標比長度要-1。
{
printf("只能在允許的下標範圍內插入元素[0, %d]\n", MAX_SIZE - 1);
return;
}
if(index > seqList -> length) //下標不應該大於順序表的元素個數
{
printf("插入的下標超過了數組的最大長度1,插入失敗!\n");
return;
}
//在c89 標準中,直接在for 循環中定義變量是不允許的;
//在c99 標準以後就允許。
for(int i = seqList -> length - 1; i >= index; i--)
{
seqList -> datas[i + 1] = seqList -> datas[i];
}
//5.將要插入的值賦值給第index 個元素。
seqList -> datas[index] = element;
//6.順序表長度length + 1!!!- 一定要注意不要遺漏這條語句!
seqList -> length++;
}
/**
* 刪除順序表中指定下標的元素
* @param seqList 要操作的順序表
* @param index 要刪除的下標
* @return 返回刪除的元素,如果刪除失敗,則返回NULL(建議使用完畢後進行free,否則會造成內存泄露)
*/
ElementType * DeleteElement(SeqList * seqList, int index)
{
//1.判斷下標是否合法
if(index < 0 || index > MAX_SIZE - 1)
{
printf("下標越界,無法刪除指定下標的元素!\n");
return NULL;
}
//2.找到要刪除的元素並保存起來,以便返回(保存的是已刪除的副本);
ElementType * delElement = (ElementType*)malloc(sizeof(ElementType));
//單獨定義並調用查找函數,返回要刪除元素的指針
*delElement = *GetElement(seqList, index);
//3.從指定位置刪除,後面一個元素賦值給前面一個元素;
for(int i = index; i > seqList -> length - 1; i++)
{
seqList -> datas[i] = seqList -> datas[i + 1];
}
//4.順序表的總長度-1。
seqList -> length--;
return delElement; //建議使用完畢後進行free,否則會造成內存泄露
}
/**
* 返回順序表中指定下標的元素
* @param seqList 要操作的順序表
* @param index 要返回元素的下標
* @return 返回指定下標的元素,如果查找失敗,則返回NULL
*/
ElementType * GetElement(SeqList * seqList, int index)
{
//判斷下標是否合法
if(index < 0 || index > MAX_SIZE - 1)
{
printf("下標越界,無法查找指定下標的元素!\n");
return NULL;
}
ElementType * element; //要查找的元素
element = &seqList -> datas[index];
}
/**
* 打印seqList
*/
void PrintfList(SeqList * seqList)
{
for(int i = 0; i < seqList -> length; i++)
{
printf("%d\t%s\n", seqList -> datas[i], seqList -> datas[i].name);
}
}
#endif // SEQUENCELIST_C_INCLUDED
名詞解釋:
- malloc函數:動態內存分配。
- 內存泄露:指定的內存用完後沒有釋放,導致這塊內存無法使用。
3.總結順序表的優缺點
3.1 優點
- 無需爲表示表中元素之間的邏輯關係而增加額外的存儲空間
- 可以快速地存取表中任意位置的元素
3.2缺點
- 插入和刪除操作需要移動大量的元素
- 當線性表長度變化較大時,難以確定存儲空間的容量
- 造成空間的“碎片”
好了,看到這裏的時候。我們就要開始我們的老規矩準備說再見了,希望大家可以多多練習這兩個算法。畢竟成功是沒有捷徑的。
下一期我會爲大家帶來線性表家族的另一個成員“鏈表”。
如果覺得本篇文章對你有幫助的話,麻煩各位兄弟萌點個贊幫忙轉發一下~