1 线性表
必会概念:
链式存储 前驱 后继 表长
链表的本质:
LinkList L; L = create_LinkList(10);
此时L看起来是一个线性表,其实它本质只是一个节点的引用,指向线性表表头节点.通过这个引用我们可以遍历整张线性表
1.1数据结构如何应用到运算
逻辑结构定义算法,存储结构运行算法
线性表常用逻辑算法:
- 初始化:Init_List(L) 构造一个空表
- 求表长:Length_List(L) 返回表含有的元素的个数
- 取表元:Get_List(L,i) L非空,i正确时,返回地i个表元的值或地址
- 查找(定位):已知值,查下标. Locate_List(L,x) 返回x在L中首次出现的序号或地址,否则返回-1
- 插入:Insert_List(L,i,x) L存在,i正确.是L[i]=x; L.len()++; L[i]之后的表元下标依次加一
- 删除: Delete_List(L,i) L存在,i正确时 L[i] = L[i+1] ;L.len()-- ;L[i]之后的表元下标依次减一
1.2 顺序表
列表在内存中按连续物理地址存储,知道了地址就一定能访问到对应的元素
优点与好处
- 已知表头地址Loc(
$a_0$
),表元类型(所占字节) d比特,则瞬间可求第(i+1)个元素的地址.Loc($a_i$
)=Loc($a_0$
)+i*d , 0≤i≤n - 所以顺序存储用来查找,访问,修改元素非常方便
1.3 顺序表的实现
距离是元素间相距长度,个数是下标相减再加1.
Capacity(L) = L.MAXSIZE ;下标n必须时刻≤MAXSIZE ;设插入下标为i,最末下标为n; i必须时刻≤n+1;
1.3.1 动态初始化
- 构造一个空表.
- 动态分配存储空间,先确定表容量,但具体地址对应的表元在后续代码里确定
- 定义表指针last=-1,随L.len()增加而增加
1.3.2 插入运算
- 用for循环实现:j=n+1 从
$L(n)$
起,元素依次向后挪动一位.$L(j)$
=$L(j-1)$
; j–; - 插入x: L(i) = x;
- 修改表尾指针:L.last = L.len()++;
- 算法复杂度计算: 主要耗时都发生在元素移动时.每次移动(n-i+1)个元素,即移动(n-i+1)次,假设插入到底i个元素是等概率事件,则对应的概率
$P_i$
=1/(n+1). - 总移动次数 =
$\sum_{i=1}^{n+1}=P_i(n-i+1)=\frac{n}{2}$
,即时间复杂度是O(n)
1.3.3 删除操作
- 0≤i≤n,否则报错,已经包含了i=-1,L为null时的情景.
- for循环实现:j=;j≤n;从L[i]起,L[j] = L[j+1];j++;
- 算法复杂度:O(n)
1.3.4 查找操作
找列表中值为x的第一个元素下标
- for循环,j=0; L[j]==x ; 否则j++;
- 若j==n,返回"无"
- 算法复杂度:`
1.4 线性表的链式存储与运算实现
先修知识
malloc与new的区别
new会自动根据类型分配内存地址,并返回同类型的指针给引用
int *p1,*p2;
p1 = new int;
p2 = new int[100];
malloc只能动态分配内存,意思就是不会自动分配,必须人为设定内存大小与返回的指针类型
int *p;
p = (int*)malloc(sizeof(int)*100);
->的用法
->是间接引用符,是二目访问符,类似于成员符 .
typedef struct Node{
int a;
double b;
}node, *linkList;
*linkList = &node;
//struct Node *p = &node;
//此时
linkList->a等价于(*linkList).a;
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,编译器只是读取它,并不会为它赋予内存空间.逻辑合理,它只是一个模板.
1 线性表
必会概念:
链式存储 前驱 后继 表长
链表的本质:
LinkList L; L = create_LinkList(10);
此时L看起来是一个线性表,其实它本质只是一个节点的引用,指向线性表表头节点.通过这个引用我们可以遍历整张线性表
1.1数据结构如何应用到运算
逻辑结构定义算法,存储结构运行算法
线性表常用逻辑算法:
- 初始化:Init_List(L) 构造一个空表
- 求表长:Length_List(L) 返回表含有的元素的个数
- 取表元:Get_List(L,i) L非空,i正确时,返回地i个表元的值或地址
- 查找(定位):已知值,查下标. Locate_List(L,x) 返回x在L中首次出现的序号或地址,否则返回-1
- 插入:Insert_List(L,i,x) L存在,i正确.是L[i]=x; L.len()++; L[i]之后的表元下标依次加一
- 删除: Delete_List(L,i) L存在,i正确时 L[i] = L[i+1] ;L.len()-- ;L[i]之后的表元下标依次减一
1.2 顺序表
列表在内存中按连续物理地址存储,知道了地址就一定能访问到对应的元素
优点与好处
- 已知表头地址Loc(
$a_0$
),表元类型(所占字节) d比特,则瞬间可求第(i+1)个元素的地址.Loc($a_i$
)=Loc($a_0$
)+i*d , 0≤i≤n - 所以顺序存储用来查找,访问,修改元素非常方便
1.3 顺序表的实现
距离是元素间相距长度,个数是下标相减再加1.
Capacity(L) = L.MAXSIZE ;下标n必须时刻≤MAXSIZE ;设插入下标为i,最末下标为n; i必须时刻≤n+1;
1.3.1 动态初始化
- 构造一个空表.
- 动态分配存储空间,先确定表容量,但具体地址对应的表元在后续代码里确定
- 定义表指针last=-1,随L.len()增加而增加
1.3.2 插入运算
- 用for循环实现:j=n+1 从
$L(n)$
起,元素依次向后挪动一位.$L(j)$
=$L(j-1)$
; j–; - 插入x: L(i) = x;
- 修改表尾指针:L.last = L.len()++;
- 算法复杂度计算: 主要耗时都发生在元素移动时.每次移动(n-i+1)个元素,即移动(n-i+1)次,假设插入到底i个元素是等概率事件,则对应的概率
$P_i$
=1/(n+1). - 总移动次数 =
$\sum_{i=1}^{n+1}=P_i(n-i+1)=\frac{n}{2}$
,即时间复杂度是O(n)
1.3.3 删除操作
- 0≤i≤n,否则报错,已经包含了i=-1,L为null时的情景.
- for循环实现:j=;j≤n;从L[i]起,L[j] = L[j+1];j++;
- 算法复杂度:O(n)
1.3.4 查找操作
找列表中值为x的第一个元素下标
- for循环,j=0; L[j]==x ; 否则j++;
- 若j==n,返回"无"
- 算法复杂度:`
1.4 线性表的链式存储与运算实现
先修知识
malloc与new的区别
new会自动根据类型分配内存地址,并返回同类型的指针给引用
int *p1,*p2;
p1 = new int;
p2 = new int[100];
malloc只能动态分配内存,意思就是不会自动分配,必须人为设定内存大小与返回的指针类型
int *p;
p = (int*)malloc(sizeof(int)*100);
->的用法
->是间接引用符,是二目访问符,类似于成员符 .
typedef struct Node{
int a;
double b;
}node, *linkList;
*linkList = &node;
//struct Node *p = &node;
//此时
linkList->a等价于(*linkList).a;
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,编译器只是读取它,并不会为它赋予内存空间.逻辑合理,它只是一个模板.
栈
后进先出,前插法
抽象:表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;
}
队列
后进后出,尾插法
表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++;
}
//定义表尾节点的后继节点为空.
}
1.4.1 节点
单链表由一个个节点构成
typedef struct node{
int data;
struct node *next;
}LNode
node LNode = {};