week 5 Data Structurs
The trade-offs between different data structures
场景:当用户在使用语音唤醒语音助手时,会出现无法唤醒的情况,为了解决这一情况,需要选择怎样的数据结构?
需要考虑以下几个方面的问题:
- 快速删除
- 快速插入
- 快速搜索
首先看链表:
对于链表来说,插入数据是最快的,可以直接插入到最前面;但是链表在搜索和删除的速度较慢
哈希表:
搜索和插入速度最快,这里用到了索引的概念;但是这样的数据结构会占用更多的内存,即使有些索引后面没有内容,但还是需要占用内存存放该索引
Trie:
搜索和插入速度最快;但是这样会占用大量的内存,并且工程师需要花更多的时间去构造这个复杂的数据结构
Node
通过观察上面的数据结构,发现它们都有一个称为节点的共同特征,节点的模板可以用下面代码表示:
typedef struct node
{
string phrase;
struct node *next; // 这是一个指向结构体的指针
}
node;
Stacks and Queues
FIFO (first in first out):queue,先进先出
LIFO (last in first out):stacks,后进先出
typedef struct
{
person people[CAPACITY];
int size;
}
stack
// 这里 CAPACITY 和 size 之间有什么区别?
// CAPACITY 表示总共能存多少
// size 表示当前的量是多少
Resizing Arrays
创建一个数组(不推荐这种方式,只是用于课堂演示):
#include <stdio.h>
int main(void)
{
int list[3];
list[0] = 1;
list[1] = 2;
list[2] = 3;
for (int i = 0; i < 3; i++)
{
printf("%i\n", list[i]);
}
}
通过指针和分配内存的方式,优化代码(不推荐这种方式,只是用于课堂演示):
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *list = malloc( 3 * sizeof(int));
// 这里判断是否还有多的内存,有的话才能继续后面的程序
if (list == NULL)
{
return 1;
}
list[0] = 1;
list[1] = 2;
list[2] = 3;
// 将这个数组增加一位
int *tmp = malloc( 4 * sizeof(int));
if (tmp == NULL)
{
// 如果没有内存,tmp 没有分配成功,就将之前分配的释放,然后结束程序
free(list);
return 1;
}
// 复制
for (int i = 0; i < 3; i++)
{
tmp[i] == list[i];
}
tmp[3] = 4;
// 将 list 原来 3 个数组的内存空间释放
free(list);
// Remember list of size 4
list = tmp;
for (int i = 0; i < 4; i++)
{
printf("%i\n", list[i]);
}
// 释放,这里释放 list tmp 均可,因为两个指向的同一个内存空间
// 写程序释放非临时变量更好
free(list);
return 0;
}
Modify your code as follows:
C comes with a very useful function called realloc
that will reallocate the memory for you. realloc
takes two arguments. First, it asks you to specify the array you are attempting to copy. Second, it asks you to specify the size to which you would like the final array to be.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *list = malloc( 3 * sizeof(int));
// 这里判断是否还有多的内存,有的话才能继续后面的程序
if (list == NULL)
{
return 1;
}
list[0] = 1;
list[1] = 2;
list[2] = 3;
// 将这个数组增加一位
int *tmp = realloc(list, 4 * sizeof(int));
if (tmp == NULL)
{
// 如果没有内存,tmp 没有分配成功,就将之前分配的释放,然后结束程序
free(list);
return 1;
}
list = tmp;
list[3] = 4;
for (int i = 0; i < 4; i++)
{
printf("%i\n", list[i]);
}
// 释放,这里释放 list tmp 均可,因为两个指向的同一个内存空间
// 写程序释放非临时变量更好
free(list);
return 0;
}
int *tmp = realloc(list, 4 * sizeof(int));
// 这里为什么要重新分配赋值给一个新的变量,而不是直接写成 realloc(list, 4 * sizeof(int))
// 直接写会将改变直接作用到 list 上,这时我们并不确定这个一定是成功的,要是不成功,list 原来指向的内存里的值都直接被 NULL 覆盖了,所以需要有一个变量来接收返回值,确认分配成功后再进行更新
Notice that int *tmp = realloc(list, 4 * sizeof(int))
creates a list of size four integers. Then, it copies the values of list
to this new array. Finally, a pointer called tmp
points to the location of memory of this new array. The copying is handled by realloc
. Once that copy is made, the memory at the location of list
is freed. Then, the pointer called list
is pointed at the location of tmp
, where the new array is located.
Linked Listed
节点用代码表示:
typedef struct node
{
int number;
struct node *next;
}
node;
链表的缺陷:
- 使用了两倍的内存
- 由于其是不连续的,所以无法进行索引
- 性能降低:无法使用二分搜索,因为两个数据之间可能会有不同数量的内存数据
创建链表的过程:
-
创建数据类型为 node 的变量,这时里面都是垃圾数据
-
创建一个临时变量
-
对 n 里面的变量 number 赋值
-
对 n 里面的变量 next 赋值
-
将 list 指向临时变量 n 的位置
-
再创建一个临时变量
-
对 n 里面的变量 number 赋值
-
对 n 里面的变量 next 赋值
-
n.next 指向和 list 一样的内存地址
-
将 list 指向临时变量 n 的位置
通过代码实现上述功能:
#include <stdio.h>
#include <stdlib.h>
// 链表节点的数据构造
typedef struct node
{
int number;
struct node *next;
}
node;
int main(int argc, char *argv[])
{
// 创建初始链表
node *list = NULL;
// 通过输入参数进行循环,第 0 个数据是运行文件名
for (int i = 1; i < argc; i++)
{
int number = atoi(argv[i]);
node *n = malloc(sizeof(node));
if (n == NULL)
{
return 1;
}
n->number = number;
n->next = list;
list = n;
}
// 打印
node *ptr = list;
while (ptr != NULL)
{
printf("%i\n", ptr->number);
ptr = ptr->next;
}
// 另一种写法
//for (node *ptr = list; ptr != NULL; ptr = ptr->next)
// 释放内存
ptr = list;
while (ptr != NULL)
{
// 这里要先把指向下一个数据的指针记录下来,不然释放内存后就没有了
node *next = ptr->next;
free(ptr);
ptr = next;
}
}
list_append:
// 往后添加元素
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int number;
struct node *next;
}
node;
int main(int argc, char *argv[])
{
node *list = NULL;
// 对每个输入的数据进行处理
for (int i = 1; i < argc; i++)
{
// 将输入的数字转换成 Int 类型并保存下来
int num = atoi(argv[i]);
// 将输入的数据保存
node *n = malloc(sizeof(node));
if (n == NULL)
{
return 1;
}
n->number = num;
n->next = NULL;
// 如果本来就是空列表,则直接添加即可
if (list == NULL)
{
list = n;
}
// 若不是空列表,则找到末尾再添加
else
{
for (node *ptr=list; ptr != NULL; ptr = ptr->next)
{
if (ptr->next == NULL)
{
ptr->next = n;
// 这里找到以后必须要跳出
// 不然,这时后面已经添加了一个,经过ptr = ptr->next又指向最后一个,会进行无限添加
break;
}
}
}
}
// 打印
for (node *ptr=list; ptr != NULL; ptr = ptr->next)
{
printf("%i\n", ptr->number);
}
// 释放内存
node *ptr = list;
while (ptr != NULL)
{
node *next = ptr->next;
free(ptr);
ptr = next;
}
}
list-sort:
// 按元素的大小放置到相应的位置
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int number;
struct node *next;
}
node;
int main(int argc, char *argv[])
{
node *list = NULL;
// 对每个输入的数据进行处理
for (int i = 1; i < argc; i++)
{
// 将输入的数字转换成 Int 类型并保存下来
int num = atoi(argv[i]);
// 将输入的数据保存
node *n = malloc(sizeof(node));
if (n == NULL)
{
return 1;
}
n->number = num;
n->next = NULL;
// 如果本来就是空列表,则直接添加即可
if (list == NULL)
{
list = n;
}
// 如果添加的数字是最小的
else if (n->number < list->number)
{
n->next = list;
list = n;
}
// 如果添加的数字大小不是最小的
else
{
for (node *ptr = list; ptr != NULL; ptr = ptr->next)
{
// 最大的那个
if (ptr->next == NULL)
{
ptr->next = n;
break;
}
// 在中间
if (n->number < ptr->next->number)
{
n->next = ptr->next;
ptr->next = n;
// 要插入的元素找到了相应的位置就不需要再继续循环下去了
break;
}
}
}
}
// 打印
for (node *ptr=list; ptr != NULL; ptr = ptr->next)
{
printf("%i\n", ptr->number);
}
// 释放内存
node *ptr = list;
while (ptr != NULL)
{
node *next = ptr->next;
free(ptr);
ptr = next;
}
}
list-free:
#include <stdio.h>
#include <stdlib.h>
// 链表节点的数据构造
typedef struct node
{
int number;
struct node *next;
}
node;
void free_list(node *list);
int main(int argc, char *argv[])
{
// 创建初始链表
node *list = NULL;
// 通过输入参数进行循环,第 0 个数据是运行文件名
for (int i = 1; i < argc; i++)
{
int number = atoi(argv[i]);
node *n = malloc(sizeof(node));
if (n == NULL)
{
free_list(list);
return 1;
}
n->number = number;
n->next = list;
list = n;
}
// 打印
node *ptr = list;
while (ptr != NULL)
{
printf("%i\n", ptr->number);
ptr = ptr->next;
}
// 另一种写法
//for (node *ptr = list; ptr != NULL; ptr = ptr->next)
// 释放内存
free_list(list);
}
void free_list(node *list)
{
while (list != NULL)
{
node *next = list->next;
free(list);
list = next;
}
}
Trees
二叉搜索数:Binary search trees
实现的代码如下:
// 创建一个二叉树
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct node
{
int number;
struct node *left;
struct node *right;
}
node;
void free_tree(node *root);
void print_tree(node *root);
int search_tree(node *tree, int num);
int main(void)
{
// 创建里面没有任何数据的数
node *tree = NULL;
// 添加第一个数
node *n = malloc(sizeof(node));
if (n == NULL)
{
return 1;
}
n->number = 2;
n->left = NULL;
n->right = NULL;
tree = n;
// 添加左边第一个数
n = malloc(sizeof(node));
if (n == NULL)
{
// 这里不能直接返回,需要把之前用到的所有空间都释放才行
// 写一个专门释放空间的函数
free_tree(tree);
return 1;
}
n->number = 1;
n->left = NULL;
n->right = NULL;
tree->left = n;
// 同理,添加右边第一个数
n = malloc(sizeof(node));
if (n == NULL)
{
// 这里不能直接返回,需要把之前用到的所有空间都释放才行
// 写一个专门释放空间的函数
free_tree(tree);
return 1;
}
n->number = 3;
n->left = NULL;
n->right = NULL;
tree->right = n;
// 打印函数
print_tree(tree);
// 搜索该二叉树,暂不支持搜索不存在的
int search_num;
printf("search_num: ");
scanf("%i", &search_num);
int result = search_tree(tree, search_num);
printf("%i\n", result);
// 释放内存函数
free_tree(tree);
return 0;
}
void free_tree(node *root)
{
if (root == NULL)
{
return;
}
free_tree(root->left);
free_tree(root->right);
// 这里是 free 函数,不需要再递归了
free(root);
}
void print_tree(node *root)
{
if (root == NULL)
{
return;
}
print_tree(root->left);
// 这里只需要打印一次,因为每个树的根节点只有一个
printf("%i\n", root->number);
print_tree(root->right);
}
// 搜索该二叉树
int search_tree(node *tree, int num)
{
if (tree == NULL)
{
return 1;
}
else if (num < tree->number)
{
return search_tree(tree->left, num);
}
else if (num > tree->number)
{
return search_tree(tree->right, num);
}
else if (num == tree->number)
{
return 0;
}
}