好久没熬夜了(两天嘿嘿嘿)
好久没摸键盘了(一个星期左右吧)
借着兴奋劲,来写写博客,记录学习的瞬间

刚刚查了一些其他的资料和文章,发现很多都是直接搬过来的,而且还有好多搬错了…

先来看看线性表的定义:
一个线性表是n个数据元素的有限序列,可以表示成:
(2,3,6,8,9)
在稍复杂的线性表中,一个数据元素可以由若干个数据项组成,在这种情况下,常把数据元素称为记录,含有大量记录的线性表又称文件(吧啦吧啦好多)

直接开门见山:线性表的链式表示和实现

导言:线性表的链式存储结构是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素与其直接后继数据元素之间的逻辑关系,对数据元素来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息。这两部分信息组成数据元素的存储映像,称为结点(node),他包括数据域和指针域

typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, *LinkList;

假设L是LinkList型的变量,则L是单链表的头指针,他指向表中第一个结点。若L为空(NULL),则所表示的线性表为空表,其长度为0。有时,我们在单链表的第一个结点之前附设一个结点,称之为头结点,头结点的数据域可以不存储任何信息,也可存储线性表的长度等类的附加信息,头结点的指针域存储指向第一个结点的指针。
这个分不带头结点和带头结点

bool InitList1(LinkList& L) {
// 此时他是一个没有头结点的单链表
L = NULL;
return true;
}
bool InitList2(LinkList& L) {
// 此时他是一个有头结点的单链表
LinkList p = new LNode();
if (L == NULL) return false; // 分配内存失败
L->next = NULL; // 头结点之后暂时没有结点
return true;
}
int main(void) {
// 此处仅仅是声明
LinkList L;
return 0;
}

用插入操作来区分一下他们:

// 带头结点
bool ListInsert(LinkList& L, int i, ElemType e) {
// 头结点下标是0,不允许用此结点替代头结点
if (i < 1) return false;
LNode* p;
int j = 0;
p = L;
while (p != NULL && j < i -1)
p = p->next;
j++;
if (p == NULL) return false;
LNode* s = new LNode();
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
// 不带头结点
bool ListInsert(LinkList& L, int i, ElemType e) {
if (i < 1) return false;
// 不带头结点的时候,此时L即指向
if (i == 1) {
LNode* s = new LNode();
s->data = e;
s->next = L;
L = s;
return true;
}
LNode* p;
int j = 0;
p = L;
while (p != NULL && j < i -1)
p = p->next;
j++;
if (p == NULL) return false;
LNode* s = new LNode();
s->data = e;
s->next = p->next;
p->next = s;
return true;
}

下面的操作都是以有头结点来操作的

查找一个数据是否在单链表中

void Search_List(LinkList& L, int n, bool& judge) {
int i = 0;
while (i < n) {
L = L->next;
if (L->data == n) {
judge = true;
break;
}
else {
judge = false;
i++;
continue;
}
}
return -1;
}

插入一个元素到链表某个位置

void InsertList(LinkList& L, int n, int m, bool& judge) {
int i = 0;
while (i < n) {
L = L->next;
if (i == m) {
LinkList s = new LNode();
s->data = n;
s->next = L->next;
L->next = s;
}
else {
judge = false;
continue;
}
}
return -1;
}

删除某个结点(同上,不讲了(傲娇!))

有时,也可以借用一维数组来描述线性链表,就是静态链表
数组的一个分量表示一个结点,同时用游标(指示器cur)代替指针指示结点在数组中的相对位置(也就是下一个结点的位置)
优点:在插入和删除的时候无需移动元素,仅需修改游标
缺点:要事先分配一个较大的空间,不可更改

#define MAXSIZE 100
typedef struct {
elemType data;
int cur;
}component, SLinkList[MAXSIZE];

查找一个在静态链表的元素

int LocateList(SLinkList S, ElemType e) {
int i = S[0].cur; // 获得第一个结点的地址
while (i && S[i].data == e) {
// 得到下一个指向结点的地址
i = S[i].cur;
}
return i;
}

插入一个元素,同时也附上其他操作

// 初始化备用空间
void InitSpace(SLinkList& S) {
for (int i = 0; i < MAXSIZE; ++i) {
space[i].cur = i+1;
}
space[MAXSIZE-1].cur = 0;
}
// 生成S的头结点
int Malloc_SL(SLinkList& S) {
int i = S[0].cur;
if (space[0].cur) {
space[0].cur = space[i].cur;
}
return i;
}
// 将空闲结点链结到备用链表上
void Free_SL(SLinkList& S, int k) {
S[k].cur = S[0].cur;
S[0].cur = k;
}
Status ListInsert(StaticLinkList space, int i, Elemtype e) //插入元素
{
int j,k,l;
k = MAXSIZE - 1;
if (i<1 || i>ListLength(space) + 1)
return ERROR;
j = Malloc_SL(space);
if (j)
{
space[j].data = e;
for (l = 1; l <= i - 1; l++)
k = space[k].cur;
space[j].cur = space[k].cur;
space[k].cur = j;
return OK;
}
else
return ERROR;
}

循环链表:circular linked list,他的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环,由此,从表中任意节点结点出发均可找到表中其他结点。
当有头指针的时候,尾指针指向的是头指针;没有头指针的时候,尾指针指向的是第一个结点。

双向链表:上述链表的特点是找其他结点只能从前往后出发,若要寻找结点的直接前趋,则需要从表头指针出发。双向链表的结点中有两个指针域,其一指向直接后继,另一指向直接前驱

typedef struct DuLNode {
ElemType data;
struct DuLNode *prior, *next;
}DuLNode, *DuLinkList;

下面以插入操作为例

bool ListInsert_DuL(DuLinkList& L, int i, ElemType e){
// 在带头结点的双向链表L的第**i个位置之前**插入元素e
// 一大堆判断条件我就直接省略了蛤
// 在L中确定插入位置p
DuLinkList p = GetElem(L, i);
DuLinkList s = new DuLNode();
s->data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return ok;
}