数据结构之线性表

1 线性表

必会概念:

链式存储 前驱 后继 表长

链表的本质:

LinkList L; L = create_LinkList(10);
此时L看起来是一个线性表,其实它本质只是一个节点的引用,指向线性表表头节点.通过这个引用我们可以遍历整张线性表

1.1数据结构如何应用到运算

逻辑结构定义算法,存储结构运行算法

线性表常用逻辑算法:

  1. 初始化:Init_List(L) 构造一个空表
  2. 求表长:Length_List(L) 返回表含有的元素的个数
  3. 取表元:Get_List(L,i) L非空,i正确时,返回地i个表元的值或地址
  4. 查找(定位):已知值,查下标. Locate_List(L,x) 返回x在L中首次出现的序号或地址,否则返回-1
  5. 插入:Insert_List(L,i,x) L存在,i正确.是L[i]=x; L.len()++; L[i]之后的表元下标依次加一
  6. 删除: Delete_List(L,i) L存在,i正确时 L[i] = L[i+1] ;L.len()-- ;L[i]之后的表元下标依次减一

1.2 顺序表

列表在内存中按连续物理地址存储,知道了地址就一定能访问到对应的元素

优点与好处

  1. 已知表头地址Loc($a_0$),表元类型(所占字节) d比特,则瞬间可求第(i+1)个元素的地址.Loc($a_i$)=Loc($a_0$)+i*d , 0≤i≤n
  2. 所以顺序存储用来查找,访问,修改元素非常方便

1.3 顺序表的实现

距离是元素间相距长度,个数是下标相减再加1.

Capacity(L) = L.MAXSIZE ;下标n必须时刻≤MAXSIZE ;设插入下标为i,最末下标为n; i必须时刻≤n+1;

1.3.1 动态初始化

  1. 构造一个空表.
  2. 动态分配存储空间,先确定表容量,但具体地址对应的表元在后续代码里确定
  3. 定义表指针last=-1,随L.len()增加而增加

1.3.2 插入运算

  1. 用for循环实现:j=n+1 从$L(n)$起,元素依次向后挪动一位.$L(j)$ = $L(j-1)$; j–;
  2. 插入x: L(i) = x;
  3. 修改表尾指针:L.last = L.len()++;
  4. 算法复杂度计算: 主要耗时都发生在元素移动时.每次移动(n-i+1)个元素,即移动(n-i+1)次,假设插入到底i个元素是等概率事件,则对应的概率$P_i$=1/(n+1).
  5. 总移动次数 = $\sum_{i=1}^{n+1}=P_i(n-i+1)=\frac{n}{2}$,即时间复杂度是O(n)

1.3.3 删除操作

  1. 0≤i≤n,否则报错,已经包含了i=-1,L为null时的情景.
  2. for循环实现:j=;j≤n;从L[i]起,L[j] = L[j+1];j++;
  3. 算法复杂度:O(n)

1.3.4 查找操作

找列表中值为x的第一个元素下标

  1. for循环,j=0; L[j]==x ; 否则j++;
  2. 若j==n,返回"无"
  3. 算法复杂度:i=0nPii=n+12=O(n)\sum_{i=0}^nP_ii=\frac{n+1}{2}=O(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数据结构如何应用到运算

逻辑结构定义算法,存储结构运行算法

线性表常用逻辑算法:

  1. 初始化:Init_List(L) 构造一个空表
  2. 求表长:Length_List(L) 返回表含有的元素的个数
  3. 取表元:Get_List(L,i) L非空,i正确时,返回地i个表元的值或地址
  4. 查找(定位):已知值,查下标. Locate_List(L,x) 返回x在L中首次出现的序号或地址,否则返回-1
  5. 插入:Insert_List(L,i,x) L存在,i正确.是L[i]=x; L.len()++; L[i]之后的表元下标依次加一
  6. 删除: Delete_List(L,i) L存在,i正确时 L[i] = L[i+1] ;L.len()-- ;L[i]之后的表元下标依次减一

1.2 顺序表

列表在内存中按连续物理地址存储,知道了地址就一定能访问到对应的元素

优点与好处

  1. 已知表头地址Loc($a_0$),表元类型(所占字节) d比特,则瞬间可求第(i+1)个元素的地址.Loc($a_i$)=Loc($a_0$)+i*d , 0≤i≤n
  2. 所以顺序存储用来查找,访问,修改元素非常方便

1.3 顺序表的实现

距离是元素间相距长度,个数是下标相减再加1.

Capacity(L) = L.MAXSIZE ;下标n必须时刻≤MAXSIZE ;设插入下标为i,最末下标为n; i必须时刻≤n+1;

1.3.1 动态初始化

  1. 构造一个空表.
  2. 动态分配存储空间,先确定表容量,但具体地址对应的表元在后续代码里确定
  3. 定义表指针last=-1,随L.len()增加而增加

1.3.2 插入运算

  1. 用for循环实现:j=n+1 从$L(n)$起,元素依次向后挪动一位.$L(j)$ = $L(j-1)$; j–;
  2. 插入x: L(i) = x;
  3. 修改表尾指针:L.last = L.len()++;
  4. 算法复杂度计算: 主要耗时都发生在元素移动时.每次移动(n-i+1)个元素,即移动(n-i+1)次,假设插入到底i个元素是等概率事件,则对应的概率$P_i$=1/(n+1).
  5. 总移动次数 = $\sum_{i=1}^{n+1}=P_i(n-i+1)=\frac{n}{2}$,即时间复杂度是O(n)

1.3.3 删除操作

  1. 0≤i≤n,否则报错,已经包含了i=-1,L为null时的情景.
  2. for循环实现:j=;j≤n;从L[i]起,L[j] = L[j+1];j++;
  3. 算法复杂度:O(n)

1.3.4 查找操作

找列表中值为x的第一个元素下标

  1. for循环,j=0; L[j]==x ; 否则j++;
  2. 若j==n,返回"无"
  3. 算法复杂度:i=0nPii=n+12=O(n)\sum_{i=0}^nP_ii=\frac{n+1}{2}=O(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 = {};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章