数据结构(一)线性表静态链表
(一)前提
若是没有指针,如何实现单链表
使用数组来代替指针来描述单链表,叫做静态链表
实现方法叫做游标实现法
(二)静态链表的实现结构
(三)要点
1.我们对数组的第一个和最后一个元素做特殊处理,他们的data不存放数据 2.我们通常把未使用的数组元素称为备用链表 3.数组的第一个元素,即下标为0的那个元素的cur用来存放备用链表的第一个结点的下标 4.数组的最后一个元素,即下标为MAXSIZE-1的cur存放第一个有数值的元素的下标,相当于头结点作用
(四)静态链表的实现步骤
(1)中间插入实现(将B插入A,C中间)
1.函数Molloc_SLL获取空闲分量下标,从数组[0]处获取,此时为3
int Malloc_SLL(StaticLinkList space) { int i = space[0].cur; //获取当前数组的第一个元素中存放的可用下标 if (space[0].cur) //若是下标可用,若是指向0,数组空间全部用尽 space[0].cur = space[i].cur; //会自动更新0处的空闲分量游标 return i; }
j = Malloc_SLL(space); //获取空闲分量的下标
2.将B赋值数值下标为3的数据中
space[j].data = e; //将数据赋值给此分量
3.开始修改游标顺序(类似于链表),先使用循环获取要插入位置前的元素游标(即A的位置)
k = MAXSIZE - 1; //k首先是最后一个元素的下标 //其中i是我们要插入的位置 for (l = 1; l <= i - 1; l++) //找到第i个元素的之前的位置 k = space[k].cur;
4.将A的游标指向B的下标,将B的下标指向原来A的游标
space[j].cur = space[k].cur; //k是A的下标,j是我们新插入的元素下标 space[k].cur = j;
(2)头插法插入
代码和上面一致
//在第i个元素之前插入新的元素,变为新的i Status ListInsert(StaticLinkList space, int i, ElemType e) { int j, k, l; k = MAXSIZE - 1; //k首先是最后一个元素的下标 if (i<1 || i>ListLength(space) + 1) return ERROR; j = Malloc_SLL(space); //获取空闲分量的下标 if (j) { space[j].data = e; //将数据赋值给此分量 for (l = 1; l <= i - 1; l++) //找到第i个元素的之前的位置 k = space[k].cur; space[j].cur = space[k].cur; //把第i个元素之前的cur赋值给新的元素的cur space[k].cur = j; return OK; } return ERROR; }
1.插入C,先获取空闲分量下标为1,将e放入数据中。
2.注意:对于for循环l=1;l<=i-1;这里i=1所以不会进入循环
3.C元素的游标就是space[k].cur,k=MAXSIZE-1,所以C元素游标为0
4.space[k].cur = j;将最后数组游标设为1
(3)尾插法
代码还是一致的
//在第i个元素之前插入新的元素,变为新的i Status ListInsert(StaticLinkList space, int i, ElemType e) { int j, k, l; k = MAXSIZE - 1; //k首先是最后一个元素的下标 if (i<1 || i>ListLength(space) + 1) return ERROR; j = Malloc_SLL(space); //获取空闲分量的下标 if (j) { space[j].data = e; //将数据赋值给此分量 for (l = 1; l <= i - 1; l++) //找到第i个元素的之前的位置 k = space[k].cur; space[j].cur = space[k].cur; //把第i个元素之前的cur赋值给新的元素的cur space[k].cur = j; return OK; } return ERROR; }
1.当插入第一个数A时,和上面的头插法是一样的A的游标为0
2.当插入第二个数B时,考虑此时传入的i是2,k开始时MAXSIZE-1,需要经过一次for循环,k变为1
3.for循环后
space[j].cur=space[k].cur; //j是2空闲分量,是元素B,他的游标变为A的游标0
space[k].cur=j; //space[k].cur是A的游标变为j=2是指向B的
上面就是相当于链表的插入
(五)代码实现
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <time.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 1000 //假设链表的最大长度是1000 typedef int ElemType; typedef int Status; typedef struct { ElemType data; int cur; //游标,为0时表示无指向 }Component,StaticLinkList[MAXSIZE]; //typedef int Num[100]; //声明Num为整型数组类型名 //四个基本操作,初始,清空,判断是否为空,获取长度 Status InitList(StaticLinkList space); Status ClearList(StaticLinkList space); Status ListEmpty(StaticLinkList space); int ListLength(StaticLinkList space); //四个元素操作,插入,删除,两种查找 Status GetElem(StaticLinkList space, int i, ElemType* e); int LocateElem(StaticLinkList space, ElemType e); Status ListInsert(StaticLinkList space, int i, ElemType e); Status ListDelete(StaticLinkList space, int i, ElemType* e); //为静态链表分配可用下标 int Malloc_SLL(StaticLinkList space); //为静态链表的空闲结点进行回收到备用链表 void Free_SLL(StaticLinkList space, int k); //用来打印链表 void PrintList(StaticLinkList space); int main() { StaticLinkList Sl; ElemType e; Status ret; int i, j; printf("1.InitList\n"); InitList(Sl); printf("2.1 Insert range of 5 elements by head\n"); for (i = 1; i <= 5;i++) { ListInsert(Sl, 1, i); } printf("2.2 Insert range of 5 elements by end\n"); for (; i <= 10; i++) { ListInsert(Sl, ListLength(Sl) + 1, i); } PrintList(Sl); printf("3. list length:%d\n", ListLength(Sl)); printf("4.find element by element:%d get index:%d\n", 8, LocateElem(Sl, 8)); GetElem(Sl, 6, &e); printf("5.find element by index:%d get element:%d\n",6,e); ListDelete(Sl, 3, &e); printf("6.delete element by index:%d get element:%d\n",3,e); PrintList(Sl); printf("7.Clear\n"); ClearList(Sl); printf("8.is empty:%d\n", ListEmpty(Sl)); system("pause"); return 0; } //静态链表的初始化 Status InitList(StaticLinkList space) //传入的是数组类型,退化为指针 { int i; for (i = 0; i < MAXSIZE - 1; i++) space[i].cur = i + 1; space[MAXSIZE - 1].cur = 0; //初始化使还没有数据,所以这时头结点指针指向0,代表空 return OK; } //清空静态链表,就是将首尾两个数组设置为原始的1和0 Status ClearList(StaticLinkList space) { space[0].cur = 1; space[MAXSIZE - 1].cur = 0; return OK; } //判断是否为空,就是判断首尾数组是否还原 Status ListEmpty(StaticLinkList space) { if (space[0].cur != 1 || space[MAXSIZE - 1].cur != 0) return FALSE; return TRUE; } //由于最后一个元素游标是0,我们对游标进行循环,为0退出,获取长度 int ListLength(StaticLinkList space) { //其实可以在第一个数组中数据域中用来存放数据长度 int length = 0; int index = space[MAXSIZE - 1].cur; while (index) { index = space[index].cur; length++; } return length; } //根据索引获取元素 Status GetElem(StaticLinkList space, int i, ElemType* e) { int index = space[MAXSIZE - 1].cur; //获取到第一个结点的下标 int j = 1; if (i<1 || i>ListLength(space)) return ERROR; while (index) { if (j++ == i) { *e = space[index].data; break; } index = space[index].cur; //下一个节点坐标 } return OK; } //根据元素获取索引 int LocateElem(StaticLinkList space, ElemType e) { int index = space[MAXSIZE - 1].cur; //获取到第一个结点的下标 int j = 1; while (index) { if (space[index].data == e) break; index = space[index].cur; //下一个节点坐标 j++; } if (j > ListLength(space)) return ERROR; return j; } //获取备份链表的第一个可用下标 int Malloc_SLL(StaticLinkList space) { int i = space[0].cur; //获取当前数组的第一个元素中存放的可用下标 if (space[0].cur) //若是下标可用,若是指向0,数组空间全部用尽 space[0].cur = space[i].cur; return i; } //在第i个元素之前插入新的元素,变为新的i Status ListInsert(StaticLinkList space, int i, ElemType e) { int j, k, l; k = MAXSIZE - 1; //k首先是最后一个元素的下标 if (i<1 || i>ListLength(space) + 1) return ERROR; j = Malloc_SLL(space); //获取空闲分量的下标 if (j) { space[j].data = e; //将数据赋值给此分量 for (l = 1; l <= i - 1; l++) //找到第i个元素的之前的位置 k = space[k].cur; space[j].cur = space[k].cur; //把第i个元素之前的cur赋值给新的元素的cur space[k].cur = j; return OK; } return ERROR; } //将回退首数组的数据,指向我们删除的那个空间,不会造成内存碎片 void Free_SLL(StaticLinkList space, int k) { space[k].cur = space[0].cur; //将我们删除的这个元素游标指向原来的下一个空闲分量,方便一会回到原来的位置 space[0].cur = k; //将我们删除的那个元素放入到0下标的游标中,在下一次插入时会被调用,存放数据,节约空间 } //删除在链表中的第i个元素 Status ListDelete(StaticLinkList space, int i, ElemType* e) { int j,index; if (i<1 || i>ListLength(space)) return ERROR; index = MAXSIZE - 1; //注意下标index开始和j的关系,我们要删除第三个,j会循环两次,我们若是从index = space[MAXSIZE-1].cur(是指向第一个)开始,循环两次会直接指向我们要删除的那个,而不是我们想要的前一个 for (j = 1; j < i; j++) index = space[index].cur; //找到我们要删除的元素的前一个i-1元素的下标 j = space[index].cur; //用来保存原来删除的那个元素下标 space[index].cur = space[j].cur; //使前一个元素游标指向要删除元素下一个元素的下标,跳过中间这个要删除的 Free_SLL(space, j); //将我们删除的那个元素下标放入到备用链表中 return OK; } //按照链表顺序进行打印,根据元素的游标是否为0判断是否打印完成 void PrintList(StaticLinkList space) { int start, index; start = space[MAXSIZE - 1].cur; index = start; printf("printf list begin\n"); while (index) { printf("%5d ", space[index].data); index = space[index].cur; } printf("\nprintf list end\n"); }
(六)优缺点
优点(和单链表一样)
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的确定。
缺点(比单链表还惨)
没有解决连续存储分配(数组)带来的表长难以确定的问题
失去了顺序存储结构的随机存取的特性
(七)总结
静态链表是为了给没有指针的编程语言设计的一种实现单链表功能的方法。在支持单链表的语言中用不到他。不过方法应该了解
(八)思考
这里对静态链表操作,发现对于链表的各种插入方法有点生疏,
对于头插法,尾插法,中间插入,思考方法不一样,但是实现代码往往是一致的,
我需要去思考一下,是不是只考虑简单的中间插入,即可完成上面的多种插入方法
(九)解决
不要多想,结合前面的线性表链式存储实现,查看其它插入方法,更加简单。 方法无论哪种插入方法,无非都是: 1.获取我们要插入的位置 2.循环去获取我们要插入位置的前一个节点,如A节点 3.我们只需要先将插入的节点的next(这里是游标)指向A节点的原来下一个节点B 4.然后再将A的游标指向这个新的节点即可。 这种思路可以实现头插法,尾插法,中间插入。而中间插入最为容易思考,所以以后可以直接思考这种方法即可。对于删除操作大致相同,不再详说