0.数据结构
-
程序设计=算法+数据结构+编程范式
-
程序=算法+数据结构
算法合理利用CPU的计算资源,数据结构合理利用存储资源 -
数据结构=结构定义+结构操作
-
如何学习一种新的数据结构?
数据结构是定义一种性质并维护这种性质
⭐线性表:具有相同数据类型的N(N>=0)个数据元素的有序序列
1.顺序表
1.1定义
-
通过命令
ulimit -s
查看linux的默认栈空间大小,默认情况下为 8192 即8M -
函数中定义的数组位于栈段
-
栈段存管理节点地址,管理节点、数据都在堆段
-
结构操作:增删改查
☞删除指当前内存可以被占用,不是真正意义上的删除,如果被覆写就找不回来了
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;
}
-
动态申请内存使用 alloc 一族,配对使用 free 释放内存,避免内存泄露
-
void *malloc(size_t size)
开辟空间不清空
void *calloc(size_t nitems, size_t size)
开辟空间并清空
void *realloc(void *ptr, size_t size)
智能地开辟一段空间 -
realloc(src,size)
- 在原空间src后扩建,成功后返回src;
- 原地址扩建失败后申请新空间,成功后顺序拷贝原空间,自动 free(src) 并返回新地址
- 申请新空间失败,返回NULL
-
开辟空间时严谨的做法,是动态减小开辟空间的大小
🌙原空间大小为n,新开辟新空间大小为n+n(2倍)
🌙失败时改为n+n/2(1.5倍)
🌙再失败时改为n+n/4(1.25倍)…
2.链表
2.1定义
-
【数据搬移】顺序表在插入和删除时移动大量元素,造成浪费,解决方法:链式存储
-
虚拟头节点:位于管理节点,相当于第-1个节点,目的是统一第0个节点的插入、删除操作
-
⭐插入/删除的关键
<1> 找到待插入/删除节点的前一个节点
<2> 有序修改指针,否则会内存泄漏 -
单向循环链表:头指针指向尾结点,方便头插和尾插(尾节点是头节点和尾插位置的前一个节点)
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;
}