目录

Harvard CS50 学习笔记(五)

摘要
Harvard CS50 学习笔记(五)。

5 Data Structures

5.1 Lecture

5.1.1 Introduction

5.1.2 Arrays

5.1.3 Arrow Syntax

  1. * 的三种用途:
    1. 乘号。
    2. 声明指针变量(有数据类型)。
    3. 引用解析(置于指针前,无数据类型)。
  2. -> 符号。

5.1.4 Linked Lists

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_1.png
linked_list_1
/images/Harvard_CS50/Lecture_5/linked_list/linked_list_2.png
linked_list_2
/images/Harvard_CS50/Lecture_5/linked_list/linked_list_3.png
linked_list_3
  • 链表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// node *list;
// 创建一个链表
node *list = NULL;
// 创建一个节点
node *n = malloc(sizeof(node));
// malloc 函数常规判断
// if (n != NULL)
// {
//     (*n).number = 1;
// }
if (n != NULL)
{
    // 节点的数字部分赋值为 1 
      n->number = 1;
      // 节点的指针部分赋值为 NULL  
    n->next = NULL;
}
// 将链表变量指向第一个节点
list = n;
// 再创建一个节点,还是n变量,指向不同的节点
node *n = malloc(sizeof(node));
if (n != NULL)
{
    n->number = 2;
    n->next = NULL;
}
// 添加一个节点
list->next = n;
// 又加了一个节点
node *n = malloc(sizeof(node));
if (n != NULL)
{
    n->number = 3;
    n->next = NULL;
}
// 再添加一个节点
list->next->next = n;

5.1.5 list.c

/images/Harvard_CS50/Lecture_5/list_c/list_c_1.png /images/Harvard_CS50/Lecture_5/list_c/list_c_2.png /images/Harvard_CS50/Lecture_5/list_c/list_c_3.png /images/Harvard_CS50/Lecture_5/list_c/list_c_4.png
  • 注意几个点:
    1. 这里并没有用到链表,还是之前的数组,只不过使用了动态分配的内存。
    2. 使用 malloc 动态分配的内存位于堆,直接静态声明的变量,内存位于栈。
    3. 不需要释放 temp,因为 temp 的地址复制给了 list所以 listtemp 指向同一个地址,释放 list 足矣。

5.1.6 Valgrind

5.1.7 realloc

/images/Harvard_CS50/Lecture_5/realloc.png
realloc
  1. realloc 函数会自动帮数组扩容。
  2. 如果原数组的后续内存不够,realloc 会自动拷贝,并且返回新数组的内存地址,同时释放原数组的内存。

5.1.8 Inserting into a Linked List

非常清晰的图解:

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_6.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_7.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_8.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_9.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_10.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_11.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_12.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_13.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_14.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_15.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_16.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_17.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_18.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_19.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_20.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_21.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_22.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_23.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_24.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_25.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_26.png

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_27.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_28.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_29.png

5.1.9 Building a Linked List

/images/Harvard_CS50/Lecture_5/linked_list/linked_list_4.png /images/Harvard_CS50/Lecture_5/linked_list/linked_list_5.png
  1. 注意左右写法的不同,左边是错的,右边是对的。
  2. 指针指向整个 node,而不是单纯数字或指针。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <stdlib.h>

typedef struct node
{
  int number;
  struct node *next;
}
node;

int main(void) {
  // node *list;
  // 创建一个链表
  node *list = NULL;
  // 创建一个节点
  node *n = malloc(sizeof(node));
  // malloc 函数常规判断
  // if (n != NULL)
  // {
  //     (*n).number = 1;
  // }
  if (n == NULL)
  {
      return 1;
  } 
  // 节点的数字部分赋值为 1 
  n->number = 1;
  // 节点的指针部分赋值为 NULL  
  n->next = NULL;
  // 将链表变量指向第一个节点
  list = n;

  // 再创建一个节点,还是n变量,指向不同的节点
  n = malloc(sizeof(node));
  if (n == NULL)
  {
      free(list);
        return 1;
  }

  n->number = 2;
  n->next = NULL;
  // 添加一个节点
  list->next = n;

  // 又加了一个节点
  n = malloc(sizeof(node));
  if (n == NULL)
  {
        // 注意顺序!!
        free(list -> next);    
      free(list);
        return 1;
  }

  n->number = 3;
  n->next = NULL;

  // 再添加一个节点
  list->next->next = n;

}
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_1.png
singly_linked_lists_1

5.1.10 Printing Values of a Linked List

1
2
3
4
// 打印链表
for (node *tmp = list; tmp != NULL; tmp = tem -> next) {
      printf("%i\n", tmp -> number);
}

5.1.11 Freeing a Linked List

1
2
3
4
5
6
// 释放内存
while (list != NULL) {
  node *tmp = list -> next;
  free(list);
  list = tmp;
}

5.1.12 Linked List Demonstration

5.1.13 Linked List Time Complexities

  1. $O(n)$ - Search
  2. $O(1)$ - Insert

5.1.14 Binary Search Trees

1
2
3
4
5
6
7
typedef struct node
{
    int number;
    struct node *left;
    struct node *right;
}
node;

5.1.15 tree.c

5.1.16 Searching a Binary Search Tree

5.1.17 Binary Search Tree Time Complexities

  1. $O(n)$ - Search (树不平衡的情况)
  2. $O(1)$ - Insert

5.1.18 Hash Tables

1
2
3
4
5
6
typedef struct node
{
  char word[LONGEST_WORD + 1];
  struct node *next;
}
node;
  • Java 中的 HashMap 本质其实是 Hash Table ,不单单是数组

5.1.19 Hash Functions

5.1.20 Hashing Demonstration

5.1.21 Hash Table Buckets

  • $O(n)$ - Search

5.1.22 Tries

1
2
3
4
5
6
typedef struct node
{
  bool isWord;;
  struct node *children[SIZE_OF_ALPHABET];
}
node;
  1. Trie(字典树 / 前缀树)
  2. 链表每一个节点都是一个数组。

5.1.23 Stacks and Queues

  1. Queue
    1. FIFO(First In First Out)
    2. 先进先出。
  2. Stack
    1. LIFO(Last In First Out)
    2. 先进后出。

5.1.24 Jack Learns the Facts


5.2 Shorts

5.2.1 Singly-Linked Lists

/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_1.png
singly_linked_lists_1
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_2.png
singly_linked_lists_2
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_3.png
singly_linked_lists_3
  • 单向链表:数据 + 指向下一个节点的指针。

/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_4.png
singly_linked_lists_4
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_5.png
singly_linked_lists_5
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_6.png
singly_linked_lists_6
  1. 头节点的指针非常重要,它指向整个链表。所以一般维护一个头节点指针的全局变量,需要操作指针的时候,复制一个局部变量,进行变化。
  2. C 不像 Java,有现成的数据类型给用。C 里面的数据类型要自己构建。
  3. 例如要使用链表,不能像 Java 一样 new 一个链表出来直接用,要自己先 new 一个节点,然后把各个节点,手动串联起来形成链表。

/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_7.png
singly_linked_lists_7
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_8.png
singly_linked_lists_8
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_9.png
singly_linked_lists_9
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_10.png
singly_linked_lists_10
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_11.png
singly_linked_lists_11
  • 头插,节点顺序不重要。

/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_12.png
singly_linked_lists_12
  • 调整指针指向的顺序非常重要!!一不小心就出现孤儿链表。

/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_13.png
singly_linked_lists_13
/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_14.png
singly_linked_lists_14
  • 递归删除链表节点,从右往左。

/images/Harvard_CS50/Lecture_5/singly_linked_lists/singly_linked_lists_15.png
singly_linked_lists_15
  • 单向链表无法在中间插入一个节点。

5.2.2 Hash Tables

/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_1.png
hash_tables_1
/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_2.png
hash_tables_2
  • 哈希表(数组 + 链表):查找很快,排序很慢。

/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_3.png
hash_tables_3
/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_4.png
hash_tables_4
/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_5.png
hash_tables_5
  • 好的哈希函数:
    1. 仅用数据哈希。
    2. 用全部数据哈希。
    3. 相同数据的哈希相同。
    4. 哈希出来的哈希Code在一定范围内。
    5. 相似数据的哈希差异越大越好。

/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_6.png
hash_tables_6
  • 哈希碰撞。

/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_7.png
hash_tables_7
  • 哈希碰撞解决方法一:线性调整(linear probing)
    1. 将碰撞的元素移入其他节点。
    2. 不好。

/images/Harvard_CS50/Lecture_5/hash_tables/hash_tables_8.png
hash_tables_8
  • 哈希碰撞解决方法二:链化(chaining)
    1. 数组每一个元素为链表。
    2. 好。

5.2.3 Tries

/images/Harvard_CS50/Lecture_5/tries/tries_1.png
tries_1
/images/Harvard_CS50/Lecture_5/tries/tries_2.png
tries_2
/images/Harvard_CS50/Lecture_5/tries/tries_3.png
tries_3
/images/Harvard_CS50/Lecture_5/tries/tries_4.png
tries_4
/images/Harvard_CS50/Lecture_5/tries/tries_5.png
tries_5
/images/Harvard_CS50/Lecture_5/tries/tries_6.png
tries_6

5.2.4 Data Structures

/images/Harvard_CS50/Lecture_5/data_structures/data_structures_1.png
data_structures_1
  • 4种数据类型。

/images/Harvard_CS50/Lecture_5/data_structures/data_structures_2.png
data_structures_2
  • 数组:
    1. 插入——不好。
    2. 删除——不好。
    3. 查询——好。
    4. 排序——好。
    5. 体积——小。
    6. 可扩展性——差。

/images/Harvard_CS50/Lecture_5/data_structures/data_structures_3.png
data_structures_3
  • 链表:
    1. 插入——好。
    2. 删除——好。
    3. 查询——不好。
    4. 排序——不好。
    5. 体积——小(比数组大)。
    6. 可扩展性——好。

/images/Harvard_CS50/Lecture_5/data_structures/data_structures_4.png
data_structures_4
  • 哈希表:
    1. 插入——一般(两步:哈希 + 插入)。
    2. 删除——好。
    3. 查询——好。
    4. 排序——不可用。
    5. 体积——大。
    6. 可扩展性——差。

/images/Harvard_CS50/Lecture_5/data_structures/data_structures_5.png
data_structures_5
  • 前缀树:
    1. 插入——不好。
    2. 删除——好。
    3. 查询——好。
    4. 排序——不可用。
    5. 体积——大。
    6. 可扩展性——差。

数组 链表 哈希表 前缀树
插入 不好 一般(哈希 + 插入) 不好
删除 不好
查询 不好
排序 不好 🚫不可用 🚫不可用
体积 小(大于数组)
可扩展性

5.3 Lab 5

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Simulate genetic inheritance of blood type

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

// Each person has two parents and two alleles
typedef struct person
{
    struct person *parents[2];
    char alleles[2];
}
person;

const int GENERATIONS = 3;
const int INDENT_LENGTH = 4;

person *create_family(int generations);
void print_family(person *p, int generation);
void free_family(person *p);
char random_allele();

int main(void)
{
    // Seed random number generator
    srand(time(0));

    // Create a new family with three generations
    person *p = create_family(GENERATIONS);

    // Print family tree of blood types
    print_family(p, 0);

    // Free memory
    free_family(p);
}

// Create a new individual with `generations`
person *create_family(int generations)
{
    // TODO: Allocate memory for new person
    person *child = malloc(sizeof(person));

    // Generation with parent data
    if (generations > 1)
    {
        // TODO: Recursively create blood type histories for parents
        generations--;
        person *father = create_family(generations);
        person *mother = create_family(generations);

        // TODO: Randomly assign child alleles based on parents
        int r = rand() % 2;
        child -> parents[0] = father;
        child -> parents[1] = mother;
        child -> alleles[0] = father -> alleles[r];
        child -> alleles[1] = mother -> alleles[r];

    }

    // Generation without parent data
    else
    {
        // TODO: Set parent pointers to NULL
        child -> parents[0] = NULL;
        child -> parents[1] = NULL;

        // TODO: Randomly assign alleles
        child -> alleles[0] = random_allele();
        child -> alleles[1] = random_allele();
    }

    // TODO: Return newly created person
    return child;
}

// Free `p` and all ancestors of `p`.
void free_family(person *p)
{
    // TODO: Handle base case
    if (p -> parents[0] == NULL) {
        free(p);
        return;
    }

    // TODO: Free parents
    free_family(p -> parents[0]);
    free_family(p -> parents[1]);

    // TODO: Free child
    free(p);

}

// Print each family member and their alleles.
void print_family(person *p, int generation)
{
    // Handle base case
    if (p == NULL)
    {
        return;
    }

    // Print indentation
    for (int i = 0; i < generation * INDENT_LENGTH; i++)
    {
        printf(" ");
    }

    // Print person
    printf("Generation %i, blood type %c%c\n", generation, p->alleles[0], p->alleles[1]);
    print_family(p->parents[0], generation + 1);
    print_family(p->parents[1], generation + 1);
}

// Randomly chooses a blood type allele.
char random_allele()
{
    int r = rand() % 3;
    if (r == 0)
    {
        return 'A';
    }
    else if (r == 1)
    {
        return 'B';
    }
    else
    {
        return 'O';
    }
}

5.4 Problem Set 5

  • 做这个题可以说是一波三折,本身内容不熟悉,先是计算机组成原理那边卡壳,然后状态不佳,然后陪家人,前后一共拖了2周,学的都快忘了。最后还是写出来了,还需要继续努力。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Implements a dictionary's functionality

#include <stdbool.h>
#include <stdio.h>
#include "dictionary.h"
#include <string.h>
#include <ctype.h>
#include <cs50.h>
#include <stdlib.h>

// Represents a node in a hash table
typedef struct node
{
    char word[LENGTH + 1];
    struct node *next;
}
node;

// Number of buckets in hash table
const unsigned int N = 262144;

// Hash table
node *table[N];

int total = 0;

node* createNode(char str[LENGTH + 1]);
void freeNodes(node* node);

// Returns true if word is in dictionary, else false
bool check(const char *word)
{

    char target[LENGTH + 1];
    int i = 0;

  	// 记录检索单词每个小写字母到数组
    while (word[i] != '\0') {
        target[i] = tolower(word[i]);
        i++;
    }

  	// 多余的位置用0占位,否则Hash值不同
    for (int j = i; j < LENGTH + 1; j++) {
        target[j] = 0;
    }

    int index = hash(target);
    if (table[index] == NULL) {
        return false;
    }

  	// 链表的头节点
    node* pointer = table[index];

  	// 比较单词是否相同
    while (true) {
        if (strcmp(target, pointer -> word) == 0) {
            return true;
        }
        if (pointer -> next == NULL) {
            break;
        } else {
            pointer = pointer -> next;
        }
    }
    return false;
}

// Hashes word to a number
unsigned int hash(const char *word)
{
    // BKDR Hash Function

  unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
	unsigned int hash = 0;

	while (*word)
	{
		hash = hash * seed + (*word++);
	}

	unsigned int result = (hash & 0x7FFFFFFF) / 10000;
	if (result > N) {
	    result /= 10;
	}
    return result;
}

// Loads dictionary into memory, returning true if successful, else false
bool load(const char *dictionary)
{
    FILE* file = fopen(dictionary, "r");
    if (file == NULL) {
        return false;
    }

    char str[LENGTH + 1];
    while (fscanf(file,"%s", str) != EOF) {
        total++;
        int index = hash(str);
        if (table[index] == NULL) {
          	// 第一个节点
            node* firstNode = createNode(str);

            table[index] = firstNode;
            // free(firstNode);
        } else {
          	// 头插法记录后续节点
            node* subsequentNode = createNode(str);
            strcpy(subsequentNode -> word, str);
            node* firstNode = table[index];
            subsequentNode -> next = firstNode;
            table[index] = subsequentNode;
            // free(subsequentNode);
        }
    }
    fclose(file);
    return true;

}

// Returns number of words in dictionary if loaded, else 0 if not yet loaded
unsigned int size(void)
{
    return total;
}

// Unloads dictionary from memory, returning true if successful, else false
bool unload(void)
{
    for (int i = 0; i < N; i++) {
        if (table[i] == NULL) {
            continue;
        } else {
            freeNodes(table[i]);
        }
    }
    return true;
}

// 创建节点
node* createNode(char str[LENGTH + 1]) {
    node* pointer = malloc(sizeof(node));
    if (pointer != NULL) {
        strcpy(pointer -> word, str);
        pointer -> next = NULL;
    }
    return pointer;
}

// 递归释放所有节点
void freeNodes(node* node) {
    if (node -> next == NULL) {
        free(node);
    } else {
        freeNodes(node);
    }
}