考研数据结构-线性表

线性表的基本概念与实现

1.线性表的定义

线性表是具有相同特征数据元素的一个有限序列。

元素个数叫做线性表的长度,n(n>=0)表示,n=0(空表)

2.线性表的逻辑特性

只有一个表头元素,只有一个表尾元素。表头无前驱,表尾无后继,除表头和表尾外,其他元素只有一个直接前驱,也只有一个直接后继。

3.线性表的存储结构

顺序存储结构(顺序表)和链式存储结构(链表)两种。

顺序表

​ 连续存储、顺序存储。

链表

​ 不仅有元素的信息,还有逻辑关系的信息(后继结点的地址信息)。

两种存储结构的比较

顺序表:

​ 随机访问特性、占用连续的存储空间。

​ 插入:需要移动多个元素。

链表:

​ 不支持随机访问、存储空间利用率较顺序表稍低、支持存储空间的动态分配。

​ 插入:无需移动元素。

链表的5种形式:

  1. 单链表:

    带头结点的单链表(头结点不存信息):head->next==Null,链表为空。

    不带头的单链表:head==Null,链表为空。

  2. 双链表:多了一个前驱结点

  3. 循环单链表:

    单链表最后一个指针域(空指针)指向第一个结点。若循环结点戴头结点,则最后一个指针域(空指针)指向头结点。

  4. 循环双链表:

    head==Null,不带头结点的循环双链表为空。

    带头结点,空状态下,head->next和head->prior都必然等于head。

    以下四句任何一句为真都可以判断循环双链表为空。

    head->next==head;
    head->priot==head;
    head->next==head && head->priot==head;
    head->next==head || head->priot==head;
    
  5. 静态链表

    指针指示的是链表中下一元素在数组中的地址。

    与顺序表相比优点是便于插入和删除。

说明:考研中经常考到顺序表和链表的比较,给出全面答案。

基于空间的比较

  1. 存储方式的分配:

    顺序表的存储空间是一次性分配的,链表的存储空间是多次分配的。

  2. 存储密度(存储密度=结点值域所占的存储量/结点结构所占的存储总量)

    顺序表的存储密度=1,链表的存储密度<1(因为结点中有指针域)

基于时间的比较

  1. 存取方式

    顺序表可以随机存取;

    链表只能顺序存取(遍历之前的元素)

  2. 插入/删除时移动元素的个数:

    顺序表平均需要移动近一半元素;

    链表不需要移动元素,只需要改变指针。

​ n和元素的顺序表删除和插入施加复杂度分析:

  • 求概率

    插入位置随机,n个插入位置,任一位置插入概率 p = 1/n

  • 对应位置需要移动的元素个数

    插在第i个元素之后,移动元素n-i个。

  • 移动元素的数学期望E:

    E = p(n+(n-1)+(n-2)+……+0) = (n-1)/2; O(n)

    2.2线性表的结构体定义和基本操作

    线性表的结构体定义

    #define maxSize 100
    

    1.顺序表的结构体定义

    typedef struct{
        int data[maxSize];
        int length;
    }Sqlist;
    

    说明:考试中用的最多的线性表的定义是如下形式(因为简洁):

    int A[maxSize];
    int n;//长度为n
    

    2.单链表结点定义

    typedef struct LNode{
    int data;
    struct LNode* next;
    }LNode;

    推荐这种。黄色线标注的写成一样多好。

    3.双链表定义

    typedef struct LNode{
    	int data;
    	struct LNode* prior;
    	struct LNode* next;
    }LNode;
    

    说明:分配链表结点空间时,定义一个指针,指针指向结点,常用这个指针的名称来作为结点的名称。

    LNode *A = (LNode*)malloc(sizeof(LNode))

    int p = (int)malloc(sizeof(int))

    typedef struct node{
        int x;
        int y;
    }node;
    int p = (int)malloc(sizeof(int));//ok
    node n = (node)malloc(sizeof(node));//不ok
    node no = new node;//不ok
    node *No = new node[5];//ok
    //上述几个不ok得原因没有想通。
    

    ‘*’和‘->’的区别:

    ​ 结构体变量取分量用’.’

    LNode a;
    a.data;
    

    ​ 指向结构体变量的指针取分量用’->’

    LNode *b;
    b->data;
    //(*b)就变成了结构体变量可以用'.'
    (*b).data;
    

    描述解释:

    “p指向q”,p指代指针,因为p既是指针名又是结点名,但是结点不能指向结点,因此p指代指针。又如“用函数free()释放p的空间”,此时p指代结点,因为p既是指针名又是结点名,但是指针变量自身所需的存储空间时系统分配,不需要用户调用函数free()释放,只有用户分配的存储空间才需要用户自己释放,所以p指代结点。

    补充:(仅作补充,便于加深理解。)

    有的同学可能和我一样有疑问,这个LNode,在main函数中我偏不申明为LNode*指针,非要试试声明为LNode;

    我们通过代码得出的结论是:确实结点不能指向结点,因为Lnode内的next属性是指针,我们只能让LNode*的指针来指向LNode* next(实践出真知!);

    #include 
    #include 
    using namespace std;
    typedef struct LNode{
        int data;
        struct LNode* next;
    }LNode;
    void creat_LNode(LNode &L);
    void print(LNode L);
    int  main() {
        LNode L;
        creat_LNode(L);
        print(L);
        return 0;
    }
    void creat_LNode(LNode &L){
        LNode *temp = &L;
        for(int i =0;i<10;i++){
            temp->data = i;
            LNode* temp2 = (LNode*)malloc(sizeof(sizeof(LNode)));
            temp->next = temp2;
            temp = temp2;
        }
        temp->next = NULL;
        //free(temp);此处不能free();
    }
    void print(LNode L){
        LNode *temp = &L;
        while(temp->next!=NULL){
            cout<<temp->data<<" ";
            temp = temp->next;
        }
    }
    

    顺序表的初始化:

    void initList(Sqlist &L){
    	L.length = 0;
    }
    

    “引用&的辨析”:

    顺序表插入:在p位置上插入e之后,p不正确返回0。

    void insertElem(Sqlist &L,int p,int e);

    ‘&’是引用,不要理解成地址,因为L本身需要改变所以用引用,把修改后的值带回去。如果不用引用,函数内修改的值是带不回去的。

    //p的检查
    if(p<0 || p>L.length || L.length == maxSize)
        return 0;
    

    链表插入:

    链表尾插x

    void insertElem(LNode *L,int x)

    我们对单链表L进行了修改,但是没有用&L

    void insertElem(LNode* &L,int x);

    L内部数据的更改和==&==没有关系。

    加了引用表示允许修改L指针的指向,然后把它带回到main函数中。

    如果不加引用,即使我们在插入函数内,让L重新指向一个新的指针,L修改后的值是带不到main函数中的。比如:在下列的print()函数中,函数内已经修改了L=L->next,修改后的值没有带回去。

    #include 
    using namespace std;
    typedef struct LNode{
        int data;
        struct LNode* next;
    }LNode;
    void insertElem(LNode *L,int x){
        while(L->next!=NULL){
            L=L->next;
        }
        //此时L的下一个为NULL
        L->next = (LNode*)malloc(sizeof(sizeof(LNode)));
        L->next->data = x;
        L->next->next = NULL;
    }
    void print(LNode *L){
        while (L->next!=NULL){
            L = L->next;
            cout<<L->data<<" ";
        }
        cout<<endl;
    }
    int  main() {
        LNode *L;
        L->data = -1;//头结点
        L->next = NULL;
        for(int i=0;i<10;i++){
            insertElem(L,i);
        }
       print(L);
        return 0;
    }
    

    逆置问题(408重要考点)

    void reverse(int a[],int left,int right,int k){
    	int temp;
        for(int i=left,j=right;i<left+k && i<j;i++,j--){
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    }
    
  1. 把长度n数组的前端k(k

    reverse(a,0,n - 1,k);

  2. 把长度n数组的前端k(k

    先逆序前k个再重复1的过程

    reverse(a,0,k-1,k);

    reverse(a,0,n-1,k);

  3. 数组中元素(X0,X1,…,Xn-1)变为(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1),即循环左移p个位置。

    前扔后

    要求:

    123456789 -> 456789123

    或者把123看成一个整体

    =>321456789

    =>321987654

    =>456789123

    reverse(a,0,p-1,p);
    reverse(a,p,n-1,n-p);
    reverse(a,0,n-1,n);
    

    后提前

    要求:

    123456789 -> 789123456

    把123456看成一个整体

    => 654321789

    =>654321987

    => 789123456

    reverse(a,0,n-p-1,n-p);
    reverse(a,n-p,n-1,p);
    reverse(a,0,n-1,n);
    

你可能感兴趣的:(数据结构,链表,指针,数据结构,算法)