线性表的链式存储与运算实现
1 先修知识
1.1 malloc与new的区别
new会自动根据类型分配内存地址,并返回同类型的指针给引用
int *p1,*p2;
p1 = new int;
p2 = new int[100];
malloc只能动态分配内存,意思就是不会自动分配,必须人为设定内存大小与返回的指针类型
int *p;
p = (int*)malloc(sizeof(int)*100);
1.2 ->的用法
->是间接引用符,是二目访问符,类似于成员符 .
typedef struct Node{
int a;
double b;
}node, *linkList;
*linkList = &node;
//struct Node *p = &node;
//此时
linkList->a等价于(*linkList).a;
1.3 struct的用法
自定义类型struct
typedef struct Node{
Elemtype a;
Node *next;
}node,linkList; //结构变量node等有点像静态类,这里借用java编程思想描述,Node是父类,node,linkList是子类.
或
struct Node node,linkList;//按模板类型Node定义具体类型node,linkList
写一个struct Node,编译器只是读取它,并不会为它赋予内存空间.逻辑合理,它只是一个模板.
2 节点
节点由数据域和指针域构成.指针域决定先后链接顺序,有时链接关系不好修改的,可以直接交换数据域的内容,间接达到交换节点的目的.
单链表由一个个节点构成
typedef struct node{
datatype data;
struct node *next;
}LNode
node LNode = {};
等价于
public class node{
Object data;
class node next;
}
public class LNode extends node{}
3 栈
后进先出,前插法
抽象:表L; 新节点s(malloc,s->a=x); s->next=L(指向表头);L=s(表头指向s,s再指向next里保存的旧表头地址)
linkList create_stack(int flag){
linkList L = null; //linkList是指针子类,故L也是指针,又L=null静态声明,省去了malloc分配.
// 同时! L==null;第一个节点s->next=L,巧妙地使最后一个节点的next为null.不会出现野指针.
node *s;
int x;
scanf("%d",&x);
int cout=1;
while(cout<=flag){
s=(Node*)malloc(sizeof(node));//每次循环都会手动new一个s.因为新的s节点的next后继是上一节点,所以新的s节点是上一节点的前驱.即新的s节点是前插的.满足栈后进先出的形式.
s->data=x;
s->next=L;//s上链指向L的表头,此时s还不算在L内
L = s;//让s做L的表头,即巧妙地让L永远指向新的表头元素.
scanf("%d",&x);
cout++;
}
return L;
}
4 队列
后进后出,尾插法
表L只要指向表头就够了,我们可以用副表指针*r先指向第一个节点(s作表头时),它与L都是指向第一个节点的引用.
linkList create_quene(int flag){
linkList L=null;
node *s,*r=null;
int x;
scanf("%d",&x);
int cout=1;
while(cout<=flag){
s = malloc(sizeof(node)); //new一个s,new完对s的属性赋值
s->a = x;
if(L=null) L=s;
else r->next = s;上链
r = s; //此处r,L都是s的软引用.L占了r的光,之后一条龙的链表全靠r接上.L最多指向表头,其余上链操作有r来完成
scanf("%d",&x);
cout++;
}
//定义表尾节点的后继节点为空.
}
5 利用表指针求表长
表指针顺节点往下走(赋值),表指针都是数据域为空,指针域指向当前节点,与节点同类型的内存空间.
后插法里的L,*r都是表指针,其中L作头指针变量,它所引用的节点叫头节点.即L是头节点,L->next才是第一个数据节点
int linkList_length(linkList L){
node *p = L;
int j=0;
while(p->next!=nuLl){
p=p->next;
j++;
}
return j;
}
6 前插法与后插法
*p指向某个节点,*s指向待插入的新节点
graph LR
后插法
A(节点p)-->|起初令p->next=p',得到|B(p的下一节点是p')
A-->|最终p的下一节点是s|C
C-->|第二步p->next=s|A
C(待插入节点s)-->|第一步s->next=p`|B
graph BT
前插法
B(当前节点p)-->|第一步 q=L, while \q->next!=p\ q=q->next|A(找到p的前驱q)
C-->|第二步 s->next=p|B
A-->|第三步 q->next=s|C
C(待插入节点s)
graph TB
后插+交换实现前插
A(节点p)-->|起初 p->next=p`|B(p的下一节点是p`)
A-->|交换节点数据swap\p->next,s->next\|C
C-->|第二步p->next=s|A
C(待插入节点s)-->|第一步s->next=p`|B
7 删除节点
当前节点p,前驱节点q
- 已知p,找到它的前驱q
- *q->next = *p->next
- free§
8 删除当前节点的后一节点
当前节点*p
- node *s;
- s = p->next;
- p->next = s->next;
- free(s);
9 删除表L的第i个元素
- for循环找到L[i-1]
- 若L[i-1]->next != null
- 则用删除节点法,删除第i个节点
10 单循环列表
- 尾指针指向头指针
- 判断表已经遍历一遍的方法是p->next == head,是则已到尾指针.
- 有时可用r指向尾指针,直接从尾部开始遍历,加快运算.
11 单链表总结
单链表不具备按序号随机(直接)访问元素的能力,必须先找到它的前驱节点,才能访问要访问的节点.即只能从头指针开始访问.
12 双向链表
prior | data | next |
---|
typedef struct dlnode{
datatype data;
struct dlnode *prior,*next;
}DLnode,*DLinkList
单链表能做的事它都能做,最大的区别就是每修改一次链表,必须前后节点都要定义指向一次
12.1 插入
已知p,插入s
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
s->next = p;
12.2 删除(free)
已知p,删除p
- p->prior->next = p->next;
- p->next->prior = p->prior;
- free§;
1.6 以a1为基准,划分线性表
划分就是重排列,比a1小的在前,大的在后
1.7 两线性表比较大小
A长a,B长b,两数组第一次不同时的下标i,A的剩余长度as,B的剩余长度bs,
- for循环使下标走到两数组第一次不同的地方,i
- for(j=i;j<a;j++){ AS[j-i]=A[j];a++;}
- for(j=i;j<b;j++){ BS[j-i]=B[j];b++;}
- if(asbs&&as0) return 0;//A==B
- if(as==0&&bs>0 || as>0&&bs>0&&AS[0]<BS[0]) return -1;//A<B
- else return 1;//A>B
1.8 倒转单链表
将原链表H中的每一个元素依次作为第一个元素插入到新链表中
void reverse(LinkList H){
Node *p;
p=H->next;
while§{
q=p;
p=p->next;
//插入q到头节点后面
q->next = H->next;
H->next = q;
}
}
删除表L中重复的元素
从首元素开始,遍历表L,删除与首元素相同的元素(修改链接关系),类推
两层遍历,两层while
void pur_LinkList(LinkList H){
Node *p,*q,*r;
p=H->next;
while(p->next){
q=p;
while(q->next){
if(q->next->data=p->next){
r = q->next;
q->next=r->next;
free(r);
}else{
q=q->next;//下一个对比元素
}
p=p->next;//下一个首元素
}
}
按访问权比排序链表元素
prior前驱指针域,data数据域,next后继指针域,freq权比域