[数据结构] 1.顺序表与链表

0.数据结构

  1. 程序设计=算法+数据结构+编程范式

  2. 程序=算法+数据结构
    算法合理利用CPU的计算资源,数据结构合理利用存储资源

  3. 数据结构=结构定义+结构操作

  4. 如何学习一种新的数据结构?
    数据结构是定义一种性质并维护这种性质

⭐线性表:具有相同数据类型的N(N>=0)个数据元素的有序序列

1.顺序表

1.1定义

  1. 通过命令 ulimit -s 查看linux的默认栈空间大小,默认情况下为 8192 即8M

  2. 函数中定义的数组位于栈段

  3. 栈段存管理节点地址,管理节点、数据都在堆段

  4. 结构操作:增删改查
    ☞删除指当前内存可以被占用,不是真正意义上的删除,如果被覆写就找不回来了

1.2 代码演示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef struct Vector {
    int *data, size, length;
} Vec;

//堆区
Vec *initVec(int n) {
    Vec *v = (Vec *)malloc(sizeof(Vec));
    v->data = (int *)malloc(sizeof(int) * n);
    v->size = n;
    v->length = 0;
    return v;
}

void freeVec(Vec *v) {
    if (v == NULL) return ;
    free(v->data);
    free(v);
    return ;
}

int expand(Vec *v) {
    int expsize = v->size;
    int *temp;
    while (expsize) {
        temp = (int *)realloc(v->data, sizeof(int) * (v->size + expsize));
        if (temp) break;
        expsize >>= 1;
    }
    if (!temp) return 0;
    v->data = temp;
    v->size += expsize;
    printf("expand %d bytes successfully~\n", expsize);
    return 1;
}

int insert(Vec *v, int idx, int val) {
    if (!v) return 0;
    if (idx < 0 || idx > v->length) return 0;
    if (v->length == v->size) {
        if (!expand(v)) return 0;
    }
    //memcpy(dst, src, n); 一次拷贝不会覆盖丢失数据
    //重点:下标从0开始
    memmove(v->data + idx + 1, v->data + idx, sizeof(int) * (v->length - idx));
    v->data[idx] = val;
    v->length++;
    return 1;
}

int erase(Vec *v, int idx) {
    if (!v) return 0;
    if (idx < 0 || idx >= v->length) return 0;
    memmove(v->data + idx, v->data + idx + 1, sizeof(int) * (v->length - idx - 1));
    v->length--;
    return 1;
}

void showVec(Vec *v) {
    printf("Vec:[");
    if (v) {
        for (int i = 0; i < v->length; i++) {
            i && printf(",");
            printf("%d", v->data[i]);
        }
    }
    printf("]\n");
    return ;
}

int find(Vec *v, int val) {
    if (!v) return -1;
    for (int i = 0; i < v->length; i++) {
        if (v->data[i] == val) return i;
    }
    return -1;
}

int main() {
    Vec *v = initVec(2); //栈区
    srand(time(0));
    int cnt = 10;
    while (cnt--) {
        int opt = rand() % 4;
        int val = rand() % 100;
        int idx = rand() % (v->length + 3) - 1; // [-1, v->length + 1]
        switch (opt) {
            case 0:
            case 1:
            case 2:
                printf("insert %d at %d, res = %d\n", val, idx, insert(v, idx, val));
                break;
            case 3:
                printf("erase at %d, res = %d\n", idx, erase(v, idx));
                break;
        }
        showVec(v);
    }
    insert(v, 1, 68);
    printf("find 68 at %d\n", find(v, 68));
    freeVec(v);
    return 0;
}
  1. 动态申请内存使用 alloc 一族,配对使用 free 释放内存,避免内存泄露

  2. void *malloc(size_t size)开辟空间不清空
    void *calloc(size_t nitems, size_t size)开辟空间并清空
    void *realloc(void *ptr, size_t size)智能地开辟一段空间

  3. realloc(src,size)

    1. 在原空间src后扩建,成功后返回src;
    2. 原地址扩建失败后申请新空间,成功后顺序拷贝原空间,自动 free(src) 并返回新地址
    3. 申请新空间失败,返回NULL
  4. 开辟空间时严谨的做法,是动态减小开辟空间的大小
    🌙原空间大小为n,新开辟新空间大小为n+n(2倍)
    🌙失败时改为n+n/2(1.5倍)
    🌙再失败时改为n+n/4(1.25倍)…


2.链表

2.1定义

  1. 【数据搬移】顺序表在插入和删除时移动大量元素,造成浪费,解决方法:链式存储

  2. 虚拟头节点:位于管理节点,相当于第-1个节点,目的是统一第0个节点的插入、删除操作

  3. ⭐插入/删除的关键
    <1> 找到待插入/删除节点的前一个节点
    <2> 有序修改指针,否则会内存泄漏

  4. 单向循环链表:头指针指向尾结点,方便头插和尾插(尾节点是头节点和尾插位置的前一个节点)

2.2代码演示

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef struct Node {
    int val;
    struct Node *prev, *next;
} Node;

typedef struct List {
    Node head; //虚拟头节点,当作第-1个节点
    int len;
} List;

Node *initNode(int val) {
    Node *n = (Node *)malloc(sizeof(Node));
    n->val = val;
    n->prev = n->next = NULL;
    return n;
}

void freeNode(Node *p) {
    if (p) free(p);
    return ;
}

List *initList() {
    List *l = (List *)malloc(sizeof(List));
    l->head.next = NULL; // val/prev无需初始化
    l->len = 0;
    return l;
}

void freeList(List *l) {
    if (!l) return ;
    Node *p = l->head.next, *k; // killer,记录待free的地址
    while (p) {
        k = p;
        p = p->next;
        freeNode(k);
    }
    free(l);
    return ;
}

// 插入节点到链表 「 reverse() 」
int insertNode(List *l, int idx, Node *n) {
    if (!l) return 0;
    if (idx < 0 || idx > l->len) return 0;
    Node *p = &(l->head);
    while (idx--) p = p->next; //p指向节点idx[0, idx - 1]
    n->next = p->next;
    p->next = n;
    //双向链表
    n->prev = p;
    if (n->next) //NULL
        n->next->prev = n;
    l->len++;
    return 1;
}

// 插入数值到链表
int insertVal(List *l, int idx, int val) {
    Node *n = initNode(val);
    if (!insertNode(l, idx, n)) {
        freeNode(n); //插入失败释放节点n
        return 0;
    }
    return 1;
}

// 删除节点
int erase(List *l, int idx) {
    if (!l) return 0;
    if (idx < 0 || idx >= l->len) return 0;
    Node *p = &(l->head);
    while (idx--) p = p->next;
    Node *k = p->next;
    p->next = k->next; //p->next=p->next->next;
    //双向链表
    if (k->next) //NULL
        k->next->prev = p;
    freeNode(k);
    l->len--;
    return 1;
}

void showList(List *l) {
    if (!l) return ;
    Node *p = l->head.next;
    Node *k = &(l->head); // k初始化虚拟节点地址
    printf("List+ :[");
    while (p) {
        k = p; // 取到NULL前一个节点的地址
        printf("%d->", p->val);
        p = p->next;
    }
    printf("NULL]\n");
  
    printf("List- :[");
    while (k != &(l->head)) { // 非虚拟节点,val有效
        printf("%d->", k->val);
        k = k->prev;
    }
    printf("HEAD]\n");
    return ;
}

// 头插法翻转链表
int reverse(List *l) {
    if (!l) return 0;
    Node *p = l->head.next;
    Node *k;
    // 修改管理节点
    l->head.next = NULL; // 尾节点指向NULL
    l->len = 0;
    while (p) {
        k = p;
        p = p->next;
        insertNode(l, 0, k);
    }
    return 1;
}

int main() {
    srand(time(0));
    List *l = initList();
    int cnt = 10;
    while (cnt--) {
        int val = rand() % 100;
        int opt = rand() % 5;
        int idx = rand() % (l->len + 3) - 1;
        switch (opt) {
            case 0:
            case 1:
            case 2:
                printf("insert %d at %d, res = %s\n", val, idx, insertVal(l, idx, val) ? "SUC" : "ERR");
                break;
            case 3:
                printf("erase at %d, res = %s\n", idx, erase(l, idx) ? "SUC" : "ERR");
                break;
            case 4:
                printf("reverse, res = %s\n", reverse(l) ? "SUC" : "ERR");
                break;
        }
        showList(l);
    }
    freeList(l);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值