顺序表与链表

wDao / 2023-08-16 / 原文

顺序表与链表

前言

   基础数据结构的学习主要包括两个部分,即【结构定义】与【结构操作】。顾名思义,结构定义就是定义某种或多种性质,再通过相关的结构操作去维护这种性质。对于初学者来说数据结构的学习不能抽象的理解,还需要结合动态的、可视化的工具去理解。下面给出美国旧金山大学数据结构可视化的网站,帮助理解。
   美国旧金山大学数据结构可视化网站

顺序表

【概念】

  顺序表就是线性表的顺序存储形式,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

【结构特点】

  • 支持随机存取

  • 插入删除不方便

  • 存储密度高

【结构操作】

  • 初始化【init】
Vector *init(int n) {
    Vector *v = (Vector *)malloc(sizeof(Vector));
    v->data = (int *)malloc(sizeof(int) * n);
    v->len = 0;
    v->size = n;
    return v;
}//数组空间初始化
  • 删除【delete】
int delete(Vector *v, int ind) {
    if (v == NULL) return 0;
    if (ind < 0 || ind >= v->len) return 0;
    for (int i = ind; i < v->len; i++) {
        v->data[i] = v->data[i + 1];    
    }
    v->len--;
    return 1;
}//删除指定位置的元素

  • 销毁【clear】
void clear(Vector *v) {
    if (v == NULL) return ;
    free(v->data);
    free(v);
    return ;
}//销毁顺序表
  • 插入【insert】
int insert(Vector *v, int ind, int val) {
    if (v == NULL) return 0;
    if (ind < 0 || ind > v->len) return 0;
    if (v->len == v->size) {
        //扩容操作
        if (!expand(v)) return 0;//扩容失败
        printf("expand is successfuly ~~~\n");
    }

    for (int i = v->len; i > ind; i--) {
        v->data[i] = v->data[i - 1];
    }
    v->data[ind] = val;
    v->len++;
    return 1;
}//插入元素
  • 扩容【expand】
int expand(Vector *v) {
    int tmp_size = v->size;
    int *p;
    while (tmp_size) {
        p = (int *)realloc(v->data, sizeof(int) * (tmp_size + v->size));//重新分配空间
        if (p) break;
        tmp_size /= 2;
    }
    if (p == NULL) return 0;
    v->data = p;
    v->size += tmp_size;
    return 1;
}//扩容操作
  • 查找【search】
int search(Vector *v, int ind) {
    if (v == NULL) return 0;
    if (ind < 0 || ind >= v->len) return 0;
    return v->data[ind];
}//获取指定位置元素的值

【问题记录】

  • 当使用realloc重新分配数组空间失败后,返回的是什么值?【NULL】
  • 当calloc、malloc、realloc申请的空间为0个能不能申请成功?
    • 可以申请成功,并且不为空。
    • 注意:但是此时申请的地址空间不可使用
  • 顺序表插入一个新元素val到ind位置的思路:
    1.判断ind是否合法,即0 ≤ ind < vector.len;
    2.判断顺序表的长度是否大于空间大小(vector.len ≥ vector.size)
    3.从顺序表最后一个元素开始向后移动一位,直到移动到下标为ind位置为止
    4.将vector[ind] = val; 完成顺序表元素插入
    5.vector.len += 1;
  • 顺序表删除指定位置ind的思路:
    1.判断要删除的位置ind是否合法,即 0 ≤ ind < vector.len;
    2.指针走到下标为ind的位置,将ind + 1的元素复制到ind位置,直到指针走到顺序表最后一个元素。
    3.vector.len -= 1;
  • 顺序表数组空间扩容的思路:
    1.判断顺序表长度是否等于数组空间的大小,即 vector.len ?= vector.size
    2.若len 等于 size 则触发扩容操作,将原始空间大小 size 保存下即:tmp_size = size;
    3.为了防止重新分配空间失败,申请指针p , 采用while循环通过不断缩减temp_size的大小(tmp_size /= 2)申请重新分配地址空间。
    4.退出循环后判断p是否为NULL, 不为空就将p赋值给原始指针。并更新空间大小。
  • 顺序表数组空间销毁的思路:
    1.首先判断vector是否为NULL
    2.不为空则销毁数据空间
    3.最后销毁vector
  • 顺序表查询指定位置元素的思路:
    1.判断查询位置是否合法(即:0 ≤ ind < vector.len)
    2.直接根据索引即可查询到(即:return vector.data[ind])

顺序表完整代码

点击查看代码
#include<stdio.h>
#include<time.h>
#include<stdlib.h>

/*顺序表:初始化、插入、删除、销毁、扩容、查找*/
typedef struct Vector {
      int *data;
      int len, size;
}Vector;

Vector *init(int n) {
    Vector *v = (Vector *)malloc(sizeof(Vector));
    v->data = (int *)malloc(sizeof(int) * n);
    v->len = 0;
    v->size = n;
    return v;
}//数组空间初始化

int expand(Vector *v) {
    int tmp_size = v->size;
    int *p;
    while (tmp_size) {
        p = (int *)realloc(v->data, sizeof(int) * (tmp_size + v->size));//重新分配空间
        if (p) break;
        tmp_size /= 2;
    }
    if (p == NULL) return 0;
    v->data = p;
    v->size += tmp_size;
    return 1;
}//扩容操作

int insert(Vector *v, int ind, int val) {
    if (v == NULL) return 0;
    if (ind < 0 || ind > v->len) return 0;
    if (v->len == v->size) {
        //扩容操作
        if (!expand(v)) return 0;//扩容失败
        printf("expand is successfuly ~~~\n");
    }

    for (int i = v->len; i > ind; i--) {
        v->data[i] = v->data[i - 1];
    }
    v->data[ind] = val;
    v->len++;
    return 1;
}//插入元素

int delete(Vector *v, int ind) {
    if (v == NULL) return 0;
    if (ind < 0 || ind >= v->len) return 0;
    for (int i = ind; i < v->len; i++) {
        v->data[i] = v->data[i + 1];    
    }
    v->len--;
    return 1;
}//删除指定位置的元素


int search(Vector *v, int ind) {
    if (v == NULL) return 0;
    if (ind < 0 || ind >= v->len) return 0;
    return v->data[ind];
}//获取指定位置元素的值

void clear(Vector *v) {
    if (v == NULL) return ;
    free(v->data);
    free(v);
    return ;
}//销毁顺序表

void output(Vector *v) {
    if (v == NULL) return ;
    printf("Vector[%d] ==> [", v->len);
    for (int i = 0; i < v->len; i++) {
        i && printf(",");
        printf("%d", v->data[i]);
    }
    printf("]\n");
    return ;
}//遍历顺序表

int main() {
    #define max_op 10
    Vector *v = init(max_op);/*初始化数组空间*/
    srand(time(0));
    int op, ind, val;
    for (int i = 0; i < max_op; i++) {
        op = rand() % 4;
        ind = rand() % (v->len + 1);
        val = rand() % 100;
        switch (op) {
            case 0:
            case 1:
            case 2: {
                /*插入元素*/
                printf("Vector insert val[%d] into ind[%d] is %s\n", val, ind, insert(v, ind, val) ? "successfully" : "fail");
            } break;
            case 3: {
                /*删除指定位置的元素*/
                printf("Vector delete ind[%d] is %s\n", ind, delete(v, ind) ? "successfully" : "fail");
            } break;
        }
        output(v);    
    }
    
    printf("请输入要查找的位置:");
    while (~scanf("%d", &ind)) {
        val = search(v, ind);
        if (val) printf("search ind[%d] is val[%d]\n", ind, val);
        else printf("对不起, 你查找的位置不合法。请重新输入!\n");
        printf("请输入要查找的位置:");
    }
    putchar(10);
    #undef max_op
    clear(v);/*销毁顺序表*/
    return 0;
}

链表

【概念】

  使用任意的存储单元存储线性表的数据元素,该存储单元可以是连续的也可以是不连续的。采用结点表示每一个线性表的元素,结点包括指针域、数据域。数据域存储数据元素的值、指针域存储下一个结点的地址。

【结构特点】

  • 插入删除效率高

  • 内存利用率高

  • 操作灵活

【结构操作】

  • 初始化【init】
List *init() {
    List *l = (List *)malloc(sizeof(List));
    l->head = (Node *)malloc(sizeof(Node));
    l->head->next = NULL;
    l->len = 0;
    return l;
}/*初始化链表*/
  • 插入【insert】
int insert(List *l, int ind, int val) {
    if (l == NULL) return 0;
    if (ind < 0 || ind > l->len) return 0; /*插入位置不合法*/
    Node *p = l->head, *q;
    while (ind--) p = p->next;
    q = p->next;
    p->next = getNewNode(val);
    p = p->next;
    p->next = q;
    l->len++;
    return 1;
}/*插入新节点*/
  • 销毁【clear】
void clearNode(Node *head) {
    if (head == NULL) return ;
    clearNode(head->next);
    free(head);
    return ;
}/*销毁结点*/

void clear(List *l) {
    if (l == NULL) return ;
    clearNode(l->head);
    free(l);
    return ;
}/*销毁链表*/

  • 删除【delete】
int delete(List *l, int ind) {
    if (l == NULL) return 0;
    if (ind < 0 || ind >= l->len) return 0;//删除位置不合法
    Node *p = l->head, *q;
    while (ind--) p = p->next;
    q = p->next;
    p->next = q->next;
    free(q);
    l->len--;
    return 1;
}/*删除结点*/

问题记录

  • 链表如何插入一个新节点node到指定位置ind?
    • 判断ind是否合法(即 ind > 0 && ind < list.len);
    • 申请两个指针p 、q;
    • p指向虚拟头结点,根据ind向后迭代 while (ind—)p = p→next;
    • q指向p→next(防止内存泄漏)
    • 将node插到p→next位置 :p→next = node;
    • 此时p指向 p→next; p = p→next
    • 重新挂载后面的数据 p→next = q;
  • 链表删除 指定元素?
    • 指针p走到待删除的前一个结点位置。
    • 将待删除结点的后面数据使用指针q指向
    • 将指针p指向的结点的next指针域覆盖为待删除的结点后面的q;

链表完整代码

点击查看代码【单链表】
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
/*单链表:初始化、获取新节点、插入新节点、删除结点、销毁链表、遍历结点*/
typedef struct Node {
    int data;
    struct Node *next;
}Node;

typedef struct List {
    Node *head;//虚拟头结点
    int len;
}List;

/*获取新节点*/
Node *getNewNode(int val) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->data = val;
    p->next = NULL;
    return p;
}

List *init() {
    List *l = (List *)malloc(sizeof(List));
    l->head = (Node *)malloc(sizeof(Node));
    l->head->next = NULL;
    l->len = 0;
    return l;
}/*初始化链表*/

int insert(List *l, int ind, int val) {
    if (l == NULL) return 0;
    if (ind < 0 || ind > l->len) return 0; /*插入位置不合法*/
    Node *p = l->head, *q;
    while (ind--) p = p->next;
    q = p->next;
    p->next = getNewNode(val);
    p = p->next;
    p->next = q;
    l->len++;
    return 1;
}/*插入新节点*/

int delete(List *l, int ind) {
    if (l == NULL) return 0;
    if (ind < 0 || ind >= l->len) return 0;//删除位置不合法
    Node *p = l->head, *q;
    while (ind--) p = p->next;
    q = p->next;
    p->next = q->next;
    free(q);
    l->len--;
    return 1;
}/*删除结点*/

void output(List *l) {
    if (l == NULL) return ;
    printf("Linklis[%d] == [", l->len);
    int flag = 0;
    for (Node *p = l->head->next; p; p = p->next) {
        flag && printf("->");
        printf("%d", p->data);
        flag = 1;
    }
    printf("]\n");
    return ;
}/*遍历链表结点*/

void clearNode(Node *head) {
    if (head == NULL) return ;
    clearNode(head->next);
    free(head);
    return ;
}/*销毁结点*/

void clear(List *l) {
    if (l == NULL) return ;
    clearNode(l->head);
    free(l);
    return ;
}/*销毁链表*/

int main() {    
    srand(time(0));
    int op, ind, val;
    #define max_op 20
    List *l = init();
    for (int i = 0; i < max_op; i++) {
        op = rand() % 4;
        ind = rand() % (l->len + 1);
        val = rand() % 100;
        switch (op) {
            case 0:
            case 1:
            case 2: {
                printf("Linklist[%d] insert val = %d in the ind = %d is %s\n", l->len, val, ind, insert(l, ind, val) ? "YES" : "NO");
            } break;
            case 3: {
                printf("Linklist[%d] delete the ind = %d is %s\n", l->len, ind, delete(l, ind) ? "YES" : "NO");
            } break;
        }   
        output(l);
    }
    clear(l);
    #undef max_op
    return 0;
}
点击查看代码【双向链表】
//双向链表:初始化、插入、删除、销毁、遍历、获取ind位置的元素
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

typedef struct Node {
    int data;
    struct Node *next, *pir;
}Node;

typedef struct DLinkList {
    Node *head;
    int len;
}DLinkList;

//获取新节点
Node *getNewNode(int val) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->next = p->pir = NULL;
    p->data = val;
    return p;
}

//获取新的双链表
DLinkList *getNewDLinkList() {
    DLinkList *DL = (DLinkList *)malloc(sizeof(DLinkList));
    DL->head = getNewNode(0);//头结点
    DL->len = 0;
    return DL;
}

//插入
int insert(DLinkList *DL, int ind, int val) {
    if (DL == NULL) return 0;
    if (ind < 0 || ind > DL->len) return 0;//插入位置不合法
    Node *p = DL->head, *q, *new_node = getNewNode(val);
    while (ind--) p = p->next;
    new_node->next = p->next;
    new_node->pir = p;
    if (p->next) p->next->pir = new_node;
    p->next = new_node;
    DL->len += 1;
    return 1;
}

//删除
int delete(DLinkList *DL, int ind) {
    if (DL == NULL) return 0;
    if (ind < 0 || ind >= DL->len) return 0;//删除的位置不合法
    Node *p = DL->head;
    while (ind--) p = p->next;
    Node *q = p->next;
    p->next = q->next;
    if (q->next) q->next->pir = p;
    DL->len--;
    free(q);
    return 1;
}

//搜索第ind位置的元素
int search(DLinkList *DL, int ind) {
    if (DL == NULL) return 0;
    if (ind < 0 || ind >= DL->len) return 0;
    Node *p = DL->head;
    while (ind--) p = p->next;
    return p->data;
}

//遍历
void output(DLinkList *DL) {
    if (DL == NULL) return ;
    printf("[");
    Node *p = DL->head->next;
    int i = 0;
    for (Node * p = DL->head->next; p; p = p->next) {
        i++ && printf(", ");
        printf("%d", p->data);
    }
    printf("] <== DL[%d]\n", DL->len);
    return ;
}

//销毁双向链表
void clearNode(Node *head) {
    if (head == NULL) return ;
    clearNode(head->next);
    return ;
}

void clear(DLinkList *DL) {
    if (DL == NULL) return ;
    clearNode(DL->head);
    free(DL);
    return ;
}

int main() {
    srand(time(0));
    #define max_op 20
    int op, val, ind;
    DLinkList *DL = getNewDLinkList();//获取一个双向链表
    for (int i = 0; i < max_op; i++) {
        op = rand() % 4;
        ind = rand() % (DL->len + 1);
        val = rand() % 100;
        printf("op = %d ind = %d val = %d\n", op, ind, val);
        switch (op) {
            case 0:
            case 1: {
                //插入
                printf("insert the ind = %d ,val = %d to the DL %s!\n", ind + 1, val, insert(DL, ind, val) ? "YES" : "NO");
            } break;
            case 2: {
                //删除
                printf("delete the element of ind = %d from the DL %s!\n", ind + 1, delete(DL, ind) ? "YES" : "NO");
            } break;
            case 3: {
                //查找
                val = search(DL, ind);
                printf("search the element of ind = %d from the DL is %d!\n", ind + 1, search(DL, ind));
            } break;
        }
        output(DL);
    }
    clear(DL);
    #undef max_op
    return 0;
}