データ構造の線形テーブルの逐次表現を解く記事(C言語実装)
リニアテーブル
データ構造は次の 2 つのカテゴリに分類できます。線状構造、非線形構造。
線形構造:順序付けられたデータ要素のコレクションです。例: 線形テーブル、スタック、キュー、ダブル キュー、配列、文字列、
非線形構造:ノードの前後に複数の先行ノードと後続ノードが存在する場合があります。例: グラフ、2 次元配列、多次元配列、一般化されたテーブル、ツリー (バイナリ ツリーなど)。
したがって、ここでの線形テーブルは線形構造であり、線形テーブル内の要素には先行要素が 1 つと後続要素が 1 つしかないことを意味します(咳、なんてナンセンス)同様に、データには 2 つのストレージ構造があります。順次ストレージ、チェーンストレージ。つまり、前任者と後続者の間の接続には物理的な連続性と分離との間に違いがあるため、線形テーブルも次のように分割されます。シーケンステーブル、リンクされたリスト。
線形表に関する小さな知識ポイントを簡単に紹介しましょう。
前駆体要素
A 要素が B 要素の前にある場合、A は B の前駆体要素と呼ばれます。
後続要素
B 要素が A 要素の後ろにある場合、B は A の後継要素と呼ばれます。
線形テーブルの特徴
線形テーブルは、データ要素間の「1 対 1」の論理関係によって特徴付けられます。
- 最初のデータ要素には先行がなく、ヘッド ノードと呼ばれます。
- 最後のデータ要素には後続がなく、末尾ノードと呼ばれます。
- 最初と最後のデータ要素を除き、他のデータ要素には 1 つの先行データ要素と 1 つの後続データ要素があります。
これら 2 つの線形テーブルを別々に実装しましょう
シーケンステーブル
シーケンス テーブル、つまり線形テーブルの逐次表現とは、線形テーブルのデータ要素を連続したアドレスを持つ記憶装置のグループに順番に格納することを指します。
保存された物理的位置は連続しているため、最初の要素の物理的位置がわかれば、各要素の位置を推測できます。上の図に示すように、d はテーブル内の各要素に必要な記憶単位です。
したがって、このデータ要素の位置格納アドレスは、等差数列の一般項公式に準拠します。
LOC ( ai + 1 ) = LOC ( ai ) + d LOC(a_{i+1}) = LOC(a_{i})+dLOC ( _ _ _私+ 1)=LOC ( _ _ _私は)+d
LOC ( ai ) = LOC ( a 1 ) + ( i − 1 ) ∗ d LOC(a_{i}) = LOC(a_{1})+(i - 1)*dLOC ( _ _ _私は)=LOC ( _ _ _1)+(私は−1 )∗d
シーケンステーブルの実装
数列テーブルを実現するには、まずテーブルのさまざまな機能を実現することから始め、次に数列テーブルのADT(抽象データ型)を設計します(ここでの数列テーブルは配列を使用して要素を格納および定義します)。
ADT設計
データ要素を定義する
#include<stdio.h>
//这里定义一下ID数据的最大长度,20吧
#define MAXSIZE 20
//下面这里自定义元素类型elemType
//这个顺序表就用来存储学生信息,包含学生ID与姓名
typedef struct {
int ID;
char name;
}elemType;
シーケンステーブルの配列実装
//这里的连续空间实现就是用数组
//定义表的入口
typedef struct {
ElemType data[MAXSIZE];
//定义记录表的当前长度
int length;
}sqList;
シーケンステーブルを実現するためにさまざまな関数を書き始めましょう
テーブル内の要素をクリアします
//清楚表中元素
//因为这里对表的初始化是要对表内元素的进行改变
//这里是要把地址传入,所以是传地址,而不是传形参
int clearList(sqList *sl){
//将顺序表的长度重置为0
sl -> length = 0;
return 0;
}
初期化テーブル
順序表のクリアは上記で実現されており、ここでの順序表のクリアは順序表の長さをゼロにするだけであり、ここでの初期化テーブルの動作は、まず順序表をクリアしてから各要素に初期値を代入することである。
//给函数初始化
//也是传地址
void initList(sqList *sl){
//清除表:将顺序表的长度重置为0
clearList(sl);
//遍历顺序表,并将元素的值赋为初始值(ID:0;name:'^')
for(i = 0; i < MAXSIZE; i++){
sl -> data[i].ID = 0;
sl -> data[i].name = '^';
}
return 0;
}
テーブルが空かどうかを確認する
//判断表是否为空
//这里只需要查询中数据的值,所以只需传值调用
int listEmpty(sqList sl){
if(0 == sl.length){
printf("List is empty! \n");
//为空的话返回值为1
return 1;
}
//不为空返回0
return 0;
}
テーブルの長さを返します
int listLength(sqList sl){
printf("List Length is %d ! \n", sl.length);
return sl.length;
}
テーブル内の要素を反復処理する
void listTraveral(sqList sl){
printf("List length is:%d \n",sl.length);
for(i=0; i<sl.length; i++){
printf("data[%d].ID = %d, data[%d].name = %c\n",i,sl.data[i].ID,i,sl.data[i].name);
}
}
シーケンスリストに要素を挿入する
シーケンシャルテーブルの物理空間は連続しているため、要素を挿入したい場合には、テーブルの最後の要素を記憶要素単位1つ分だけ後方に移動させると空きができ、そこに挿入したい要素を配置することができます。
ここで要素を挿入するときは、まずセキュリティ チェックと位置検出を実行する必要があります。
//插入元素
//因为这里我们要改变原始的表,所以这里我们要传值
int listInsert(sqList *sl,int pos,elemTpye e){
//安全性检测
//当表满了后,避免插入造成栈溢出
if(sl -> length == MAXSIZE){
printf("List is Full!\n");
return 2;
}
//位置检测
//插入是否符合顺序表的物理连续(最后一个元素是a[3]但是我们插入数值到a[5],将中间的a[4]给空缺,这是不允许的)
//范围应该在1<= pos <= sl->length+1
if(pos<1 || pos > (sl->length+1){
printf("Bad Position !\n");
return 3;
}
//插入操作
//注意,这里因为数组是物理存储空间连续的,所以我们应该先移动最后一个元素
for(int i = sl->length;i == pos; i--){
sl->data[i] = sl->data[i-1];
}
sl->data[pos-1] = e;
sl->length++;
printf("Insert OK!\n");
}
要素の削除
要素の削除は要素の挿入と似ていますが、前方と後方の違いが異なります。また、すべての要素を前方に移動すると、最後のメモリ領域が空くため、テーブルの長さも削除する必要があります。
//删除元素
//同样,这里需要运用传值,以改变原表中值
int listDelete(sqList *sl, int pos, elemType *e){
//安全性检测
if(sl->length == 0){
printf("List is empty!\n");
return 4;
}
//位置检测
if(pos<1 || pos>(sl->length)){
printf("Bad position!\n");
return 5;
}
//删除操作?
*e = sl->data[pos-1];
int i = 0;
for(i=pos; i<=sl->length; i++){
sl->data[i-1] = sl->data[i];
}
sl->length--;
printf("Delete OK!\n");
return 0;
}
インデックス要素
elemType listGet(sqList sl; int pos){
elemType e;
return sl.data[pos-1];
}
リスト内の要素を見つける
int listLocate(sqList sl, elemType e){
int i = 0;
for(i=0;i<sl.length;i++){
if(sl.data[i].ID == e.ID && sl.data[i].name == e.name){
printf(" Locate : %d\n",i+1);
return i+1;
}
}
return -1;
}
順序に従って先行要素を導出する
ここでの d は 2 (データ要素を格納するための最小単位) であるため、ここでは位置の値 pos-2 になります。
elemType listPriorPos(sqList sl, int pos){
// Safety test
if(pos<=1 || pos >sl.length)
return ;
else
return sl.data[pos - 2];
}
先行要素を要素ごとに導出する
上記と同様に、エクスポート時に要素を置き換えるだけです
elemType listPriorElem(sqList sl, elemType e){
int pos = listLocate(sl,e);
if(pos<=1)
return ;
else
return sl.data[pos - 2];
}
後続要素は順番に導出されます
これは上記の操作と似ています
elemType listNextPos(sqList sl, int pos){
if(pos < 1 || pos >= sl.length)
return ;
else
return sl.data[pos];
}
要素を通じて後続要素を派生する
elemType listNextElem(sqList sl, elemType e){
int pos = listLocate(sl,e);
if(pos < 1 || pos >= sl.length)
return ;
else
return sl.data[pos] ;
}
結び目
以上が数列テーブルの全内容ですが、次に線形テーブルの連鎖式を書いていきます。