数据结构之链表
2.1 单链表的定义与表示
线性表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素(可以是连续或者不连续)。所以除了存储本身信息(数据域)之外还需存储一个与后继数据元素之间关系的信息(指针域)。
这两部分组成的数据元素的存储映像,称为结点。n个结点链结成一个链表即为线性表的链式存储结构。由于只有一个指针域,又称为线性链表或单链表。
2.2 指针的常用操作
1.类型定义
typedef struct LNode{
Elemtype date;
struct Lnode *next;
}LNode,*LinkList;
2.变量定义
LinkList l;
LNode* p,* s;
3.重要操作
p = L;//p指向头结点
s = L->next;//s指向首元结点
p = p->next;//p指向下一结点
4.单链表的初始化
/*【算法步骤】
① 生成新节点作为头结点,用头指针L指向头结点
② 头结点的指针域置空
*///【算法描述】
Status intlist(ListList &L){
L = new Lnode;//L = (LinkList)mallo(sizeof(LNode));//生成新结点作为头结点,给指针;
L->next = NULL;//指针域置空
return 1;
}
5.补充基本算法
//补充 1:判断链表是否为空
//判断头结点的指针域是否为空。就是头结点指没指向别的结点?
int ListEmpty(LinkList L){
if(l->next) return 0;
else return 1;
}
//补充 2:单链表的销毁(啥都没了,头指针也没了)
/*从头指针开始依次释放所有结点。
我们的思路就是
循环条件:如果这个指针不空就执行下去 L!=NULL;或者L
① 先让P指针指向首节点 p = L;
② 让头指针指向第二个结点 L = L->next;
③ 删除这个结点 delete p;
结束条件:指针空了就停 L == NULL;或者P == NULL;
*/
Status DestroyList(LinkList &L){
LinkList p;
while(L){
p = L;
L = L->next;
delete p;
}
return 1;
}
//补充 3:清空链表(最终结果只剩下头指针)
/*从头指针开始,依次释放所有结点
我们的思路是
循环条件:只要还有链表就一直清空!p!= NULL;
① 因为要留头指针嘛所以我们从首元结点开始 p = L->next;
② 我们需要另外一个指针q帮我们存下一个结点,原因是如果先删除当前结点,那么下一个结点的地址去哪里找呢?我们找不到了,所以需要另外一个结点q存下一个结点的地址.
q = p->next; delete p;
③ 然后第二个结点是不是就变成“首元结点了呢”,所以再让p从“新的首元结点开始”,q指向下一个结点。 p = q; q = q->next;
结束条件:全删完就完事了嘛,所以当p的结点是空的时候就结束。p == NULL;或者p;
④ 让头指针的指针域指向空就完事了。L->next = NULL;
*/
Status ClearList(LinkList &L){
LinkList p,q;//LNode* p,* q;
p = L->next;
while(p){
q = p->next;
delete p;
p = q;
}
L->next = NULL;
return 1;
}
//补充 4:求单链表的表长
/*从首元结点开始,依次计数所有结点
我们的思路是
循环条件:既然要遍历那就是标志为空喽 p == NULL;或者p
① 从首元结点开始 p = L->next;
② 因为要计数所以需要一个计数器 int cut = 0;
③ 让p后移 p = p->next;
结束条件:p指向空代表链表没了所以 p == NULL;
*/
int ListLength_L(LinkList L){
LinkList p;
p = L->next;
int i = 0;
while(p){
i++;
p = p->next;
}
return i;
}
2.3 单链表基本操作的实现
1.单链表的取值(取单链表中第i个元素的内容)
/*因为链表事顺序存储不能像随机存取结构顺序表一样随便拿,所以需要遍历,然后又要取第i个元素的内容,所以需要计数器。
注意:如果正常的话他让你取的应该是在链表长度内的,如果一个链表长六个,他要是一个的话拿p到第六个往第七个走的时候就指向了空,我们就结束,如果要是小于链表长度比如0,-1,我们直接就结束。
① 从第一个结点开始顺序扫描,让p指向首元结点 p = L->next;
② 定义一个计数器,初值为1 int cut = 1;
③ 当p指向下一结点时,cut++;
结束条件:当计数器与i相等时结束 j == i;
*/
Status GetElme_L(LickListL,int i,ElemType &e){
p = L->next;
int j = 1;
while(p&&j<i){ //计数器
p = p->next;
j++;
}
if(!p || j>i) return 0; //异常事件
e = p->data; //取元素
return 1;
}
2.单链表的查找(找有没有元素与被找的相等)
按值查找-根据指定数据获得该数据所在位置(地址)
/*顺序查找 对比数据域和要求的值,返回p指针的值
我们的思路
循环条件:找到了或者没有结束
① 从第一个结点起,依次跟e比较; p = L->next;
② 找到了就返回链表中的地址如果是要位置的话,需要加上计数器;
③ 如果遍历一遍以后都没有找到与其相等的元素,就返回0或者NULL;其实就是p的值;
*/
//返回地址
LNode* LocateElem_L(LinkList L,Elemtype e){
p = L->next;
while(p && p->next!=e)
p = p->next;
return p;
}
//返回位置
int LocateElme_L(LinkList L;Elemtype e){
p = L->next; int cut = 0;
while(p && p->date!=e){
p = p->next;
cut++;
}
if(p) return cut;
else return 0;
}
3.单链表的插入(在第i个结点钱插入新结点)
/*在第i个地方插入新元素,就是让a(i-1)的指针域指新元素的数据域,让新元素的指针域指ai的数据域
我们的思路
① 找到i-1的结点 遍历嘛
② 生成数据域为e的新节点s 创建结点嘛
③ 插入新结点:
1.让新结点指向结点ai 原先ai的指针域存在ai-1的指针域里了; 所以 s->next = p->next;
2.结点ai-1的指针域指向新结点; p->next = s;
*/
Stauts Lisinsert_L(LinkList&L,int i;ElemTYpe e){
p = L;int j = 0;
while(p && j<i-1){ //寻找i-1个结点,p指向i-1结点
p = p->next;
j++;
}
if(!p || j>i-1)return 0;//非法插入
s = new LNode; s->date = e;//生成新节点s
s->next = p->next; //核心算法
p->next = s;
return 1;
}
4.单链表的删除(删除第i个结点)
/*找到第i个元素,让ai-1指向ai+1
我们的思路
① 首先找到ai-1存储位置p,如果有需要的话保存ai的值
② 让p的指针域指向ai+1,ai+1的地址在ai中; p = p->next->next;
③ 释放ai 需要事先再用一个存一下 因为删完就没了 q = p->nest; delete q;
*/
Status ListDalete_L(LinkList &L,int i,ElmeType &e){
p = L;j = 0;q;i;
while(p->next && j<i-1){p = p->next;j++;}//遍历找到i-1
if(!(p->next) || j>i-1) return 0;//异常删除
q = p->next;//临时存储要删的地址,防止被释放
p->next = q->next;//核心算法 因为提前存好了就不用p->next->next了
e = q->next;//保存删除结点的数据域,看需求的
delete q;
return 1;
}
时间效率分析
1.查找:我们需要顺着链一直找(顺序存取),最好是1,最坏是n,所以O(n);
2.插入与删除
因为线性链表不需要移动元素,只要改指针,易班是O(1);
但是如果不知道具体位置,需要查找的话,又需要从头来,所以是O(n);
5.单链表的建立—头插法(元素插入在链表头部,也叫前插法)
我们的思路
① 从一个空表开始,重复读入数据;
② 生成新结点,将读入数据存放到新结点的数据域中
③ 从最后一个结点开始,依次将各结点插入到链表的前端