线性表
1.线性表的定义 :零个或多个数据元素的有限序列
2.抽象数据类型 ==》 结构体
3 顺序存储结构
定义:用一段地址连续的存储单元依次存储线性表的数据元素
存储方式:用数组实现,线性表要有数组长度,当前长度。
3.1计算地址的方式
找到头地址,后面一个加一个单位的地址
3.2.顺序存储结构的插入和删除
3.2.1 获得元素
Status GetElem(SqList L,int i,ElemType *e){
if(L.length == 0 || i < 1 || i> L.length) return 0;
*e = L.data[i-1];
return 1;}
3.2.2插入操作
思路:看插入位置,位置不合理则返回异常。如果线性表长度大于数组长度了动态增加节点或者返回异常。如果插入位置正常,从最后一个开始向前遍历到第i个位置,依次向后移动一个位置。最后将插入元素放入i位置,线性表长度+1。
此时传入的是插入元素的值,在原来的地址上面操作值。
Status ListInsert(SqList *L,int i,ElemType e) {
int k;
if(L->length == MaxLength ) return 0; //长度达到最大
if(i < 1|| i > L->length) return 0; //超出下标
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 1;
}
3.2.3删除操作
思路:条件同上。位置正常的情况下,取出删除的元素,从删除元素位置开始遍历到最后一个元素,依次前移,表长-1。
Status ListDelete(SqList *L,int i,ElemType *e) {
int k;
if (L->length == 0) return 0;
if(i < 1|| i > L->length) return 0;
*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 1;
}
3.3顺序存储结构的优缺点
优点:
不用考虑逻辑关系增加额外的存储空间
可以快速查找
缺点:
删除和插入需要操作大量元素
元素多的时候难以确定数组长度
4 链式存储结构
定义:节点地址不一定连续,通过指针连接
第一个节点的存储位置称为头指针
头指针
指向链表中的第一个实际存储数据的节点
无论链表是否为空,头指针不为空
通过头指针才能找到链表第一个存储实际元素的节点
头节点
在单链表第一个存储目的元素节点前设置的节点,数据域一般无意义(存长度)
区别第一个节点,头节点不属于链表节点
不一定为链表必需要素,如果没有头节点,头指针直接指向a1,有就指向头节点,再由头节点的指针指向a1
4.1 单链表获取元素
1 声明指针p指向第一个节点,初始化j = 1
2 当j < i 时,遍历链表,p = p->next,j++
3 当p ==null时,说明第 i 个元素不存在
4 查找成功就返回p->data
Status GetElem(LinkList L,int i,Elemtype *e) {
int j;
LinkList p = L->next;
j = 1;
while(p && j < i) {
p = p->next;
++j;
}
if (!p || j > i) return 0;
*e = p->data;
return 1;
}
4.2单链表的插入和删除
思路:找到对应的插入或者删除位置,进行操作。
插入 将节点s插入到节点p和p->next之间
步骤:初始化指针p指向头节点,初始化j = 1
当j<i时,遍历链表,让p的指针移动,j++
p == null则说明第i个节点不存在
//插入代码核心
s->next = p->next;
p->next = s;
Status ListInsert(LinkList *L,int i,Elemtype e) {
int j;
LinkList s , p = *L;
j = 1;
while(p && j < i) {
p = p->next;
++j;
}
if (!p || j > i) return 0;
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return 1;
}
//例如i=3,则是p找到第三个节点,插入元素在第三个节点后面变成第四节点
删除算法的思路
//删除代码 找到删除元素的前驱p,删除p->next
p->next = p->next->next;
Status ListDelete(LinkList *L,int i,Elemtype *e) {
int j = 1;
LinkList p = *L,q;
while(p && j < i) {
p = p->next;
++j;
}
if (!p || j > i) return 0;
q = p->next; //这样相对于p->next = p->next->next的好处是可以保留删除节点,后面再释放
p->next = q->next;
*e = q->data;
free(q);
return 1;
}
4.3创建单链表
思路:声明指针p和计数器i;
初始化空链表L,L->next = null;
循环部分: 生成新节点赋值给p,随机生成数据赋给数据域,
//头插法
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;
p->next = *L->next;
*L->next = p; //插入到表头
}
}
//尾插法
void CreateListHead(LinkList *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;
}
4.4整表删除
Status ClearList(LinkList *L) {
LinkList p,q; //一个指针用来标记点位,一个指针用来释放
p = *L->next;
while(p) {
q = p->next; //要记录下当前节点的下一个节点,否则释放了找不到
free(p);
p=q;
}
*L->next = null;
return 1;
}
4.5游标实现静态链表
思路:建立一个大数组,第一和最后一个元素是特殊元素,不做处理,不存数据
未存数据的数组元素称为备用链表
第一个元素存放备用链表的第一个节点的下标
最后一个元素存放第一个有数值元素的下标,通常为1
typedef struct {
ElemType data;
int cur;//相当于指针了
} //用这个结构体定义数组,数组有下标,游标,数据域
Status InitList(StaticLinkList space) {
int i;
for(i=0;i<MAXSIZE-1;i++) space[i].cur = i+1;//相当于指针的p->next = q;遍历整个链表
space[MAXSIZE-1].cur = 0; //搜索回头节点,链表为空
return 1;
}
插入和删除操作
思路:模拟动态链表结构的存储空间的分配,需要时申请,无用时释放。封装函数实现
//这个函数可以返回下标值
int Malloc_SSL(StaticLinkList space) {
int i = space[0].cur; //返回第一个空闲下标
if(space[0].cur) space[0].cur = space[i].cur; //被占用了就拿下一个作为备用
return i;
//space[i]是找到元素,space[i].cur是找下一个空闲元素
}
//实现下标和游标的交换即可
//把插入的元素下标赋值给第i-1个元素的cur
//把插入元素的cur改成第i个元素的下标,实际上是相互赋值,没有实际的存储顺序改变
Status ListInsert(StaticLinkList L,int i, ElemType e){
int j,k,l;
k = MAXSIZE-1;
if(i<1 || i > ListLength(L)+1) return 0;
j = Malloc_SSL(L); //获得空闲下标
if(j) {
L[j].data = e; //赋值给空闲的data
for(l = 1;l <= i-1 ; l++) k = L[k].cur; //找到第i个元素之前的位置
L[j].cur = L[k].cur;
//把插入元素的cur改为第i个元素的下标,此时插入元素变成第i个元素,成功插入
L[k].cur = j;
//把插入元素的下标赋值给第i-1个元素的cur,第i-1个元素可以查找到插入元素
return 1;
}
return 0;
}
删除操作
void Free_SSL(StaticLinkList space,int k){
space[k].cur = space[0].cur; //把第一个元素的cur赋值给被删除元素的cur,删除元素的位置可以找到原本备用链表节点
space[0].cur = k; //被删除的元素作为新的第一个备用链表节点
}
Status ListDelete(StaticLinkList L,int i) {
int j,k;
if(i < 1 || i > ListLength(L)) return 0;
k = MAXSIZE - 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 1;
}
5 循环链表
循环链表和单链表的主要差异是循环条件的判断
单链表 判断p->next 是否为空
循环链表 判断是否等于头节点
循环链表其实就是把最后一个节点的指针指向头节点即可
合并两个循环链表
p = reaA->next; //保存头节点
reaA->next = reaB->next->next; //越过头节点,指向第一个有意义的元素
reaB->next = p; // 保留一个头节点
free(q);
6 双向链表
双向链表的结构体
typedef struct DulNode {
ElemType data;
struct DulNode *prior;
struct DulNode *next;
} DuNode, *DuLinkList;
p->next->prior = p = p->prior->next
插入操作 将s插入p和p->next
1.先搞定插入节点s的前驱后继
2.再搞定后继节点的前驱
3.最后解决前节点的后继
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
删除操作
p->prior p p->next
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);