第3章リニアテーブル
リニアフォームの3.2の定義
ゼロ以上のデータ要素の有限のシーケンス。
マークリニアテーブル(A1、...、Aの場合I. 1、A I、A I + 1、...、N-)、テーブルAのI.の1-先にA I、Aが言いましたI- 1であるI即時先行要素、I + 1があるI直接後続の要素。iは1,2 =、...、1時間N-、A I及び唯一の即時の後継者、lは、2,3 = I、...、N-、A Iとだけ直接前駆体要素。
リニアテーブル要素数nが(> = 0)リニアテーブルの長さは、N = 0、という空として定義されます。
複雑な線形テーブル、データ要素がデータ項目の数から構成されてもよいです。
3.2リニアテーブル抽象データ型
リニアテーブル抽象データ型定義:
ADT 线性表
Data
线性表的数据对象集合为{a1,a2,...,an},每个元素的类型均为DataTye。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除最后一个元素an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系时一对一的关系。
Operation
InitList(*L): 初始化操作,建立一个空的线性表L。
ListEmpty(L): 如线性表为空,返回true,否则返回false。
ClearList(*L): 将线性表清空。
GetElem(L,i,*e):将线性表L中第i个位置的元素值返回给e。
LocateElem(L,e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则
返回0表示失败。
ListInsert(*L,i,e):在线性表中第i个位置插入元素e。
ListDelete(*L,i,*e):删除线性表中第i个位置元素,并用e返回其值。
ListLength(L): 返回线性表L的元素个数。
endADT
アプリケーションごとに異なる基本操作、関係する現実的な問題の線形フォームのより複雑な操作のための線形形式、上記の基本的な動作は、達成するためにこれらの基本的な操作の組み合わせを使用することができます。
テーブルAとBの線形同じタイプの2つの要素はAにマージされます。
void unionL(List *La, List b)
{
int La_len, Lb_len, i;
ElemType e; //声明与La和Lb相同的数据元素e
La_len = ListLength(*La); //求线性表的长度
Lb_len = ListLength(Lb);
for ( i = 1; i <= Lb_len; i++)
{
GetElem(Lb, i, &e); //取Lb中第i个数据元素赋值给e
if(!LocateElem(*La, e)) //La中不存在和e相同数据元素
ListInsert(La, ++La_len, e); //插入
}
}
順次記憶構造体の3.4リニア形
3.4.1定義された順次ストレージ
線状の順次記憶構造、アドレスの使用を指す順次直線状のデータ要素を格納する連続するメモリ位置です。
3.4.2順次ストレージ
C言語順次記憶構造に実装一次元アレイ
#define MAXSIZE 20 //存储空间初始化分配量
typedef int ElemType; //ElemType类型根据实际情况而定,这里假设为int
typedef struct
{
ElemType data[MAXSIZE]; //数组存储数据元素,最大值为MAXSIZE
int length; //线性表当前长度
}SqList;
リニアアレイの長さ3.4.2表の長さの差
配列の長さは、テーブルの長さを格納するために線形メモリ空間であり、メモリの割り当ては、一般的に同じ量です。
素子長リニアテーブルが直線状に格納されたデータの数です。
3.4.3アドレスの計算方法
メモリアドレス:各メモリセル内のメモリには、この番号をアドレスと呼ばれ、独自の番号を持っています。
Cは、各データ要素は、記憶場所、I + 1つのデータ要素の格納位置と、i番目のデータ要素を満たすの格納位置関係下記(LOC関数取り出し格納された位置)を占めているものとします
LOC(I + 1)= LOC(I)+ C
LOC(I)= LOC(1)+(I-1)* C
挿入と削除3.5シーケンスストラクチャ
3.5.1は、素子動作を取得します。
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;;
/*Status 是函数的类型,其值是函数结果状态码,如OK等。
*初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
*操作结果:用e返回L中第i个数据元素的值。
*/
Status GetEleme(SqList L, int i, ElemType *e)
{
if (L.length == 0 || i < 1 || i > L.length
return ERROR;
*e = L.data[i-1];
return OK;
}
3.5.2挿入
挿入アルゴリズムのアイデア
挿入位置が不合理である場合は、例外がスローされます
アレイの直線長さは、テーブルの長さよりも大きい場合、例外がスローまたは動的に容量を増加させます。
i番目の位置に、前方横断から最後の要素は、それぞれ、彼らは戻って一つの位置を移動します。
挿入位置Iに挿入される要素。
表1を加えた長さ。
/*
*初始条件:顺序线性表L已经存在,1<=i<=ListLength(L)
*操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1。
*/
Status ListInsert(SqList *L, int i, ElemType e)
{
int k;
if (L->length == MAXSIZE) //顺序线性表已满
return ERROR;
if (i < 1 || i > L->length + 1) //i不在范围
return ERROR;
if (i <= L->length)
{
for ( k = L->length - 1; k >= i - 1; k++)
L->data[k+1] = L->data[k];
}
L->data[i-1] = e;
L->length++;
return OK;
}
3.5.3削除操作
削除アルゴリズムのアイデア
- あなたが不合理な位置を削除した場合、例外がスローされます。
- 削除データ要素を削除します。
- 最後のデータ要素を横断する位置に位置からの削除データ要素は、それぞれ、彼らは前方の一つの位置を移動します。
- テーブルの長さマイナス1。
実装コード:
/*
*初始条件:顺序线性表L已经存在,1<=i<=ListenLength(L)
*操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(SqList *L, int i, ElemType *e)
{
int k;
if (0 == L->length) //线性表为空
return ERROR;
if (i < 1 || i > L->Length) //删除位置不正确
return ERROR;
*e = L->data[i-1];
if (i<L->length)
{
for (k=i; k<L->length;k++)
L->data[k-1] = L->data[k];
}
L->length--;
return OK;
}
3.5.4最初のシーケンステーブルの長所と短所
利点 | 短所 |
---|---|
テーブルの要素や追加のストレージスペースの間の論理的関係を表現する必要はありません。 | 挿入および削除操作は、多数の素子を移動する必要があります。 |
テーブル要素の位置は、任意に高速リードアクセスであってもよいです。 | リニアテーブルの長さが大きく変化する場合、記憶空間の容量を決定することは困難です。 |
結果の収納スペース「残骸。」 |
3.6線形形式のストレージ構造
順次ストレージ・構造・ソリューションの3.6.1不足
徐庶の欠点は、挿入、削除ストレージ構造は、データ要素、ソリューションを大量に移動する必要があります。
全てのデータ要素は、それが現在のデータ要素は、次のデータ要素の位置を知るためのスペースに格納されている場所に隣接していると見なされません。
挿入:挿入されたとき、私の位置、新しい要素を挿入素子の次の要素のI-2は、元の場所に新しい要素点がi-1要素を挿入しました。
削除:要素私次の要素の位置を指すようにするとき、位置I、I-1除去要素の位置、及びI-2の削除。
ストレージ構成3.6.2リニアテーブル定義されました
各データ要素A表すためにIを、後続のデータ要素がAである。1 I +データ要素A上の間の論理的関係、Iデータ要素自体を格納することに加えて、だけでなく、その直接の後続要素の指示を格納するために必要情報。データ・フィールドと呼ばれるデータフィールドを格納する情報要素、ポインタ・フィールドと呼ばれるフィールドの直後の記憶素子。ドメインに保存されているポインタ情報がポインタまたはチェーンと呼ばれます。二つの部品情報構成データ要素A I記憶された画像は、ノード(ノード)と呼ばれます。
N個のチェーンリンク、すなわち、線形テーブルとしてノード(メモリマップをAI)(A 1、A 2、...、A N)鎖ストレージ構造は、このリスト内の各ノードのためだけそれはとても単鎖と呼ばれ、ポインタ・フィールドが含まれています。
ノード内の最初のメモリ位置は、ヘッド・ポインタ・リストと呼ばれています。
最後に、線形リストのノードポインタが「空」です。
フロントノードに取り付けられた第1の連結リストのノードでは、ヘッドノードと呼ばれます
ヘッドノードへの類似点と相違点は、3.6.3先頭ポインタ
先頭ポインタ | ヘッドノード |
---|---|
リンクされたリストがある場合、最初のノードは、ポインタへのポインタの最初のノードである場合には、最初のノードへのポインタのポインタリストの先頭を指します。 | 一般的に無意味なデータフィールドであり、均一な確立を容易にする最初のノード(リストの長さも記憶されていてもよい)の要素の前に、最初のノードを動作させます。 |
これは、ヘッドポインタがとてもよく名前の先頭ポインタリストとして知られ、役割を識別しています。 | ヘッドノードと、挿入および削除ノードの操作最初の要素ノード、その操作および統一上の他のノードの前に最初のノード。 |
リストが空であるかどうか、ヘッドポインタは空ではありません。場合は、リストの先頭ポインタの必要な要素。 | ときにヘッドノードは、必ずしも要素のリストを表示する必要はありません。 |
表の記憶域構造3.6.4リニアコード説明
単一のリストをリンクされた、構造は、C言語のポインタによって記述することができます。
/*
*线性表的单链表存储结构
*/
typedef struct Node
{
ElemType data;
struct Node *next;
} Node;
typedef struct Node *LinkList;
ノードデータフィールドは、データ記憶素子とアドレス・ポインタ・フィールドを格納する後続ノードから成ります。
以下の2つのデータ要素のリストとの関係:
3.7つの読書リスト
取得アルゴリズムは、私は、データ要素のリストを考えました:
- 最初からJを初期化する最初のノードのリンクリストへのポインタpのポインティングを宣言します。
- 当j<i时,就遍历链表,让p指针的位置向后移动,不断指向下一个结点,j累加1;
- 若链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功。
/*
*初始条件:顺序线性表L已经存在,1<=i<=ListLength(L);
*操作结果:用e返回L中第i个数据元素的值。
*/
Status GetElem(LinkList L, int i, ElemType *e)
{
int j;
LinkList p;
p = L->next;
j = 1;
while (p && j < 1)
{
p = p->next;
++j;
}
if ( !p || j > 1)
return ERROR;
*e = p->data;
return OK;
}
注:
链表带头结点,不存储任何数据,指向第一个结点。
3.8 单链表的插入与删除
3.8.1 单链表的插入
假设存储元素e的结点为s,将s插入到结点p和p->next的操作:
s->next = p->next;
p->next = s;
插入后的链表结构
单链表第i个数据插入结点的算法思路:
声明一指针p指向链表的头结点,初始化j从1开始。
当j<i时,遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1。
若到链表尾p为空,则说明第i个结点不存在;
否则查找成功,在系统生成一个空结点s;
将数据元素e赋值给s->data;
单链表插入的标准语句
s->next = p->next; p->next = s;
返回成功。
实现算法如下:
/*
* 初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
* 操作结果:在L中第i个结点位置之前插入新的数据元素e,L的长度加1。
*/
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
LinkList p, s;
p = *L;
j = 1;
while (p && j < i) //寻找i-1个结点
{
p = p->next;
++j;
}
if(!p || j > i) //第i个结点不存在
return ERROR;
s = (LinkList)malloc(sizeof(Node)); //生成新的结点(C标准函数)
s->data = e;
s->next = p->next; //将p的后继结点赋值给s的后继
p->next = s; //将s赋值给p的后继
return OK;
}
3.8.2 单链表的删除
删除结点q,需获取到p结点的指针,将前继结点p的后继结点指向q的后继结点,然后删除p。
q = p->next;
p->next = q->next;
单链表删除第i个结点的算法思路:
- 声明一指针p指向链表头指针,初始化j从1开始;
- 当j<1时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
- 若到链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功,将欲删除的结点p->next赋值给q;
- 单链表的删除标准语句p->next=q->next;
- 将q结点中的数据赋值给e,作为返回;
- 释放q结点;
- 返回成功。
实现代码算法如下:
/*
* 初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
* 操作结果:删除L的第i个结点,并用e返回其值,L的长度减1。
*/
Status ListDelete(LinkList *L, int i, ElemType e)
{
int j;
LinkList p, q;
p = *L;
j = 1;
while (p->next && j < 1) //遍历寻找第i个结点
{
p = p->next;
++j;
}
if ( ! (p->next) || j > i) //第i个结点不存在
return ERROR;
q = p->next;
p->next = q->next; //将q的后继赋值给p的后继
*e = q->data; //将q结点的数据赋值给e
free(q); //释放q结点
return OK;
}
对于插入或删除数据越频繁的操作,单链表的效率越明显。
3.9 单链表的整表创建
单链表整表创建的算法思路:
- 声明一指针p和计数器变量i;
- 初始化一空链表L;
- 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
- 循环:
- 生成一新结点赋值给p;
- 随机生成一数字赋值给p的数据域p->data;
- 将p插入到头结点和与前一新结点之间。
算法代码实现如下:
/*
* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
*/
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //先建立一个带头结点的单链表
for(i=0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node)); //生成新的结点
p->data = rand()%100 + 1; //随机生成100以内的数字
p->next = (*L)->next;
(*L)->next = p; //插入到表头
}
}
头插法:始终让新结点在第一的位置。
尾插法:每次都将新的结点插入到终端结点的后面。
/*
* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
*/
void CreateListTail(Linst *L, int n)
{
LinkList p, r;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
r = *L; //r指向尾部的结点
for(i=0; i<n; i++)
{
p = (Node*)malloc(sizeof(Node));
p->data = rand()%100 + 1;
r->next = p;
r = p; //将当前的新结点定义为表尾终端结点
}
r->next = NULL;
}
3.10 单链表的整表删除
单链表整表删除的算法思路如下:
- 声明一个结点p和q;
- 将第一个结点赋值给p;
- 循环:
- 将下一个结点赋值给q;
- 释放p;
- 将q赋值给p;
实现代码如下:
/*
* 初始条件:顺序线性表L已存在,操作结果:将L重置为空表
*/
Status ClearList(LinkList *L)
{
LinkList p, q;
p = (*L)->next; //p指向第一个结点
while(p) //没到表尾
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; //头结点指针域为空
return OK;
}
3.11 单链表结构与顺序存储结构优缺点
3.12 静态链表
静态链表:数组的元素由两个数据域组成data和cur,data存放数据元素,cur存放该数据元素后继数据元素在数组中的下标,cur叫做游标。这种用数组描述的链表叫静态链表。
/*
* 线性表的静态链表存储结构
*/
#define MAXSIZE 1000 //假设链表的最大长度为1000
typedef struct
{
ElemType data;
int cur; //游标,为0时表示无指向
} Component, StaticLinkList[MAXSIZE];
链表第一个和最后一个元素作为特殊元素处理,不存数据。通常把未使用的数据元素称为备用链表。而数组第一个元素,即下标为0的元素cur存放 备用链表的第一个结点的下标。而数组最后一个元素的cur则存放第一个有数值元素的下标。
静态链表存储如下:
/*
* 将一维数组space中各分量链成一条备用链。
* space[0].cur为头指针,"0"表示空指针
*/
Status InitList(StaticLinkList space)
{
int i;
for (i=0; i<MAXSIZE-1; i++)
space[i].cur = i+1;
space[MAXSIZE-1].cur = 0; //目前静态链表为空,最后一个元素的cur为0
return OK;
}
假设我们已将数据“甲“、”乙“、“丙”、”丁“、”戊“、”己“、庚”数据存入静态链表,则他们的状态,如下图:
3.12.1 静态链表的插入操作
分配可用结点:
/*
* 若备用空间链表非空,则返回分配的结点下标,否则返回0
*/
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur; //当前数组第一个元素的cur寸的值,就是要返回的第一个备用空闲的下标
if (space[0].cur)
space[0].cur = space[i].cur;//由于要拿出一个分量来使用了,所以就需要把下一个分量做备用。
return i;
}
插入结点
/*
* 在L中第i个元素之前插入新的数据元素e
*/
1 Status LinstInsert(StaticLinkList L, int i, ElemType e)
2 {
3 int j, k, i;
4 k = MAX_SIZE - 1;
5 if (i < 1 || i > ListLength(L) + 1)
6 return ERROR;
7 j = Malloc_SLL(L); //获得空闲分量下标
8 if (j)
9 {
10 L[j].data = e; //将数据值赋值给此分量的data
11 for(l = 1; l <= i - 1; l++)
12 k = L[k].cur;
13 L[j].cur = L[k].cur;//把第i个元素之前的cur赋值给新的元素cur
14 L[k].cur = j; //把新元素的下标赋值给第i个元素之前的cur
15 return OK;
16 }
17 return ERROR;
18 }
以下图静态链表中在"乙"和"丁"之间插入"丙":
代码解释:
当我们执行插入语句时,我们的目的时在“乙”和"丁"之间插入"丙"。调用代码时i=3。
第4行让k=MAX_SIZE - 1 = 999。
第7行,j = Malloc_SSL(L) = 7。此时下标0的cur也因为7要被占用而改备用链表值为8。
第11~12行,for循环1由1到2,执行2次。
第一次:k=L[999].cur=1
第二次:k=L[1].cur=2。
第13行,L[j].cur = L[k].cur;因为j=7,而k=2得到L[7].cur=L[2].cur=3。这就让丙的cur改为3。
第14行,L[k].cur = j;意思就是L[2].cur=7。让乙的cur指向丙的下标7。
实际插入后的效果:
3.12.2 静态链表的删除操作
删除元素的算法:
/*
* 删除L中第i个元素e
*/
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
if (i < 1 || i > ListLength(L))
return ERROR;
k = MAX_SIZE - 1;
for (j = 1; j <= i - 1; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L,j);
return OK;
}
释放空闲结点:
/*
* 将下标为k的空闲节点回收到备用链表
*/
void Free_SSL(StaticLinkList space, int k)
{
space[k].cur = space[0].cur; //把第一个元素cur值赋给要删除的分量cur
space[0].cur = k; //把要删除的分量下标赋值给第一个元素的cur
}
删除甲之后的静态链表:
获取静态链表长度
/*
* 初始条件:静态链表L已存在。
* 操作结果:返回L中数据元素的个数。
*/
int ListLength(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE - 1].cur;
while(i)
{
i = L[i].cur;
j++;
}
return j;
}
3.12.3 静态链表优缺点
优点 | 缺点 |
---|---|
挿入および削除操作は、唯一それによって順次記憶構造の挿入および削除操作の欠点を改善し、可動要素なしでカーソルを変更する必要がある場合多数の要素を移動する必要があります。 | 連続メモリ割り当ての問題を解決しないテーブルに決定する長い困難ます。 |
これは、ランダム読み取り順次記憶構造の特性を失いました。 |
3.13循環リスト
リスト全体が環を形成するように、最初のノードの終端ノードを指すように、単一のリンクリストポインタNULLポインタで終わる、これは単一のリンクリストへの尾鎖単一サイクルと呼ばれ、円形のリストをいいます。
3.14二重リンクリスト
二重リンクリスト(二重リンクリスト)は、各ノードの単一のリストであり、その前駆ノードに対するポインタフィールドポインティングを設定します。
/*
* 线性表的双向链表存储结构
*/
typedef struct DulNode
{
ElemType data;
struct DulNode *prior; //指向前驱指针
struct DulNode *next; //指向后驱指针
} DulNode, *DuLinkList;
二重にリンクされたリスト要素に
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
削除ノードP:
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);