数据结构期末复习总结及部分C语言实现

文章目录

  • 线性表 数组与链表
    • 队列 & ⭐栈 √
    • 二叉树
      • 树的遍历 √
      • ASL (Average Search Length) √
    • ⭐二叉搜索树 BST √
    • 平衡二叉树(AVL)(asl abl 旋转) √
    • ⭐哈夫曼树(HuffmanTree)√
    • 哈夫曼编码???
    • 集合
    • 基础 邻接矩阵、邻接表 √
    • BFS DFS
    • 最小生成树 (Minimum Spanning Tree) √
      • Kruskal √
      • Prim √
      • Prim 和 Kruskal 比较 √
    • 最短路径 √
      • Dijkstra算法 √
      • Floyd算法 √
    • 拓扑排序
      • AOV网 √
      • AOE网 √
  • 散列表??
  • 排序 √
    • 选择 √
      • 选择排序(Selection Sort) √
      • 堆排序(Heap Sort) √
    • 选择 √
      • 插入排序(Insertion Sort) √
      • 希尔排序(Shell Sort) √
    • 交换 √
      • 冒泡排序(Bubble Sort) √
      • 快速排序(Quick Sort) √


线性表 数组与链表

队列 & ⭐栈 √

栈stack和队列queue属于动态集合,使用策略:
stack: last-in, first-out, LIFO
queue: first-in, first-out, FIFO

  • 对于栈操作:
  1. InitStack(S):初始化空栈S;
  2. StackEmpty(S):判断一个栈是否为空;
  3. Push(&S,x):进栈,若栈未满,则将x加入使之成为新栈顶;
  4. Pop(&S,&x):出栈,若栈非空,则将栈顶元素,并用x返回;
  5. GetTop(S,&x):读栈顶元素,若栈顶元素非空,则用x返回栈顶元素;
  6. DestroyStack(&S):销毁栈,并释放栈S占用的存储空间.
  • 对于队列操作:
  1. InitQueue(&Q):初始化队列,构造一个空队列Q;
  2. QueueEmpty(Q):判断一个队列是否为空;
  3. EnQueue(&Q,x):入队,若队列未满,则将x加入使之成为新队尾;
  4. DeQueue(&Q,&x):出队,若队列非空,则将队首元素删除,并用x返回;
  5. GetHead(Q,&x):读队头元素,若栈顶元素非空,则用x返回栈顶元素.

  1. 祖先结点对应 子孙结点; 双亲结点 对应 孩子结点; 具有相同双亲的结点称为 兄弟结点;
  2. 树中一个结点的子结点个数称为结点的度;树中结点的最大度数称为树的度;
  3. 度大于0 的称为 分支结点;度为 0 的结点称为 叶子结点;
  4. 结点的深度、高度和层次
    结点的层次:从根结点开始定义的,根结点第一层,一直往下推
    结点的深度:从根结点开始自顶向下逐层相加
    结点的高度:从叶结点开始自底向上逐层相加
    树的高度: 树中结点的最大层数
  5. 有序树:树中结点的子树从左到右是有次序的,不能交换
  6. 路径和路径长度
    树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。兄弟结点之间不存在路径。
  7. 森林: 森林是由m棵互不相交的集合.

二叉树

二叉树(Binary Tree)每个节点至多只有两颗子树,并且二叉树的子树有左右之分,其次序不能任意颠倒.

A
B
D
C
F
E
G
//链式存储结构
typedef struct BiTrNode{
    ElemType data;
    struct BiTrNode *parent, *lchild, *rchild;
    int height;
}BiTrNode,*BiTree;
//顺序存储结构
int bi_tree[]={A,B,C,D,E,F,G}; //按深度顺次记录

二叉树与度为2的有序树的区别:

  • 度为2的树至少有三个结点,而二叉树可以为空
  • 度为2的有序树的孩子结点左右次序是相对的,二叉树的结点次序是确定的

树的遍历 √

先序遍历:访问根结点,先序遍历左子树,先序遍历右子树
中序遍历:中序遍历左子树,访问根结点,中序遍历右子树
后序遍历:后序遍历左子树,后序遍历右子树,访问根结点

void visit(BiTree T){
    printf("%d", T->data);
}
void PreOrder(BiTree T) {
    if (T != nullptr) {
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}
void InOrder(BiTree T) {
    if (T != nullptr) {
        InOrder(T->lchild);
        visit(T);
        InOrder(T->rchild);
    }
}
void PostOrder(BiTree T) {
    if (T != nullptr) {
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        visit(T);
    }
}

时间复杂度都为 O ( N ) O(\text{N}) O(N); 空间复杂度都为 O ( N ) O(\text{N}) O(N)

层次遍历:需要借助队列。先将二叉树根结点入队,然后出队,访问该结点。

void LevelOrder(BiTree T) {
    if (T == nullptr) {
        return;
    }
    Queue < BiTrNode * > q; //辅助队列
    q.push(T); //将根结点入队	
    while (!q.empty()) { //队列不为空时
        int sz = q.size();
        for (int i = 0; i < sz; i++) {
            BiTrNode *cur = Q.front();
            q.pop(); //队头元素出队	
            if (p->lchild != nullptr) {
                q.push(p->lchild);//左子树不为空时,左子树入队
            }
            if (p->rchild != nullptr) {
                q.push(p->rchild);//右子树不为空时,右子树入队
            }
        }
    }
}

ASL (Average Search Length) √

计算搜索关键值的均长: A S L = ∑ i = 1 n p i c i ASL=\sum^{n}_{i=1}\textit{p}_{i}\textit{c}_{i} ASL=i=1npici ,其中 p i \textit{p}_{i} pi 是查找第 i i i 个值的概率, c i \textit{c}_{i} ci 是找到第 i i i 个关键值前进行的比较次数。

  • 线性表 O ( n ) O(n) O(n)
    从根节点依次扫描,找到关键值元素即搜索成功,若扫描完成没有找到,说明搜索失败。
    成功: A S L success = ∑ i = 1 n p i c i = 1 n ∑ i = 1 n i = 1 n ⋅ n ( n + 1 ) 2 = n + 1 2 ASL_\text{success}=\sum^{n}_{i=1}\textit{p}_{i}\textit{c}_{i}=\frac{1}{n}\sum^{n}_{i=1}i=\frac{1}{n}\cdot\frac{n(n+1)}{2}=\frac{n+1}{2} ASLsuccess=i=1npici=n1i=1ni=n12n(n+1)=2n+1
    失败: A S L fail = n ASL_\text{fail}=n ASLfail=n
  • 二分法 O ( log 2 n ) O(\text{log}_2n) O(log2n)
    搜索前需进行排序,将查找内容一分为二,将关键值与中值对比,选择左子树或右子树,依次递归。树的深度是 h h h
    成功:
    A S L success = ∑ i = 1 n p i c i = 1 n ∑ i = 1 h 2 i − 1 ⋅ i = n + 1 n ⋅ log ⁡ 2 ( n + 1 ) − 1 ≈ log ⁡ 2 ( n + 1 ) − 1 \begin{aligned} \it{ASL}_\text{success}& =\sum^{n}_{i=1}\textit{p}_{i}\textit{c}_{i}\\ &=\frac{1}{n}\sum^{h}_{i=1}2^{i-1}\cdot{i}\\ &=\frac{n+1}{n}\cdot\log_2(n+1)-1\approx\log_2(n+1)-1 \end{aligned} ASLsuccess=i=1npici=n1i=1h2i1i=nn+1log2(n+1)1log2(n+1)1
    失败: A S L fail = 空 节 点 的 父 节 点 数 空 节 点 数 ASL_\text{fail}=\frac{空节点的父节点数}{空节点数} ASLfail=
  • 分块法 O ( log 2 ( m ) + N m ) O(\text{log}_2(m)+\frac{N}{m}) O(log2(m)+mN) m m m 为分块的数量, N N N 为元素的数量
    其实就是二分法的进阶版本,将带搜索序列分为多块再进行搜索。

⭐二叉搜索树 BST √

方便搜索使用,为难的是插入与删除:任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树的每一个节点的键值。
找起来很方便:

//search a given key in a given BST
struct node *search(struct node *root, int key) {
    //Base Cases: root is null or key is present at root
    if (root == NULL || root->key == key) {
        return root;
    }
    //Key is greater than root's key
    if (root->key < key) {
        return search(root->rchild, key);
    }
    //Key is smaller than root's key
    return search(root->lchild, key);
}
  • 插入新节点:从根节点开始,遇键值较大者就向左,遇键值较小者就向右,一直到尾端,即为插入点;
//insert a new node with given key in BST
struct node *insert(struct node *node, int key) {
    if (node == NULL) { //If the tree is empty, return a new node
        return newNode(key);
    }
    //Otherwise, recur down the tree
    if (key < node->key) {
        node->lchild = insert(node->lchild, key);
    } else if (key > node->key) {
        node->rchild = insert(node->rchild, key);
    }
    //return the (unchanged) node pointer
    return node;
}
  • 删除节点:如果它是叶节点,直接拿走就是了;如果它有一个节点,那就把那个节点补上去;如果它有两个节点,那就把它右节点的最小后代节点补上去
// deletes the key and returns the new root
struct node *deleteNode(struct node *root, int key) {
    if (root == NULL) { return root; } // empty
    // If key to be deleted < root's key
    // then it lies in left subtree
    if (key < root->key) {
        root->lchild = deleteNode(root->lchild, key);
    }
        // If key to be deleted > root's key
        // then it lies in right subtree
    else if (key > root->key) {
        root->rchild = deleteNode(root->rchild, key);
    }
        // if key == root's key
        // the node is to be deleted
    else {
        // node with only one child or no child
        if (root->lchild == NULL) {
            struct node *temp = root->rchild;
            free(root);
            return temp;
        } else if (root->rchild == NULL) {
            struct node *temp = root->lchild;
            free(root);
            return temp;
        }
        // node with two children:
        // Get the inorder successor
        // smallest in the right subtree
        struct node *current = root->rchild;
        // loop down to find the leftmost leaf
        while (current && current->lchild != NULL) {
            current = current->lchild;
        }
        struct node *temp = current;

        // Copy the inorder
        // successor's content to this node
        root->key = temp->key;
        // Delete the inorder successor
        root->rchild = deleteNode(root->rchild, temp->key);
    }
    return root;
}

平衡二叉树(AVL)(asl abl 旋转) √

  • 避免树的高度增长过快,降低二叉树的性能
    任意结点的左右子树高度差 ∣ hight ∣ ≤ 1 \left|\textit{hight}\right|\leq1 hight1
    操作:
平衡的
判断
旋转
插入
插入方式 描述 旋转方式
LL root的左子树节点的左子树上的节点破坏平衡 右旋转
RR root的右子树节点的右子树上的节点破坏平衡 左旋转
LR root的左子树节点的右子树上的节点破坏平衡 先左后右
RL root的右子树节点的左子树上的节点破坏平衡 先右后左
T1, T2, T3 and T4 are AVL subtrees.
1. Left Left Case 
         z                                      y 
        / \                                   /   \
       y   T4      Right Rotate (z)          x      z
      / \          - - - - - - - - ->      /  \    /  \ 
     x   T3                               T1  T2  T3  T4
    / \
  T1   T2

2. Left Right Case 
     z                               z                           x
    / \                            /   \                        /  \ 
   y   T4  Left Rotate (y)        x    T4  Right Rotate(z)    y      z
  / \      - - - - - - - - ->    /  \      - - - - - - - ->  / \    / \
T1   x                          y    T3                    T1  T2 T3  T4
    / \                        / \
  T2   T3                    T1   T2

3. Right Right Case
  z                                y
 /  \                            /   \ 
T1   y     Left Rotate(z)       z      x
    /  \   - - - - - - - ->    / \    / \
   T2   x                     T1  T2 T3  T4
       / \
     T3  T4

4. Right Left Case 
   z                            z                            x
  / \                          / \                          /  \ 
T1   y   Right Rotate (y)    T1   x      Left Rotate(z)   z      y
    / \  - - - - - - - - ->     /  \   - - - - - - - ->  / \    / \
   x   T4                      T2   y                  T1  T2  T3  T4
  / \                              /  \
T2   T3                           T3   T4

左或者右旋转:

// right rotate subtree rooted with y
struct node *rightRotate(struct node *y) {
    struct node *x = y->left;
    struct node *T2 = x->right;
    // Perform rotation
    x->right = y;
    y->left = T2;
    // Update heights
    y->height = max(height(y->left), height(y->right)) + 1;
    x->height = max(height(x->left), height(x->right)) + 1;
    // Return new root
    return x;
}
// left rotate subtree rooted with x
struct node *leftRotate(struct node *x) {
    struct node *y = x->right;
    struct node *T2 = y->left;
    // Perform rotation
    y->left = x;
    x->right = T2;
    //  Update heights
    x->height = max(height(x->left), height(x->right)) + 1;
    y->height = max(height(y->left), height(y->right)) + 1;
    // Return new root
    return y;
}

加上插入节点步骤:

// insert a key in the subtree rooted with node
// and returns the new root of the subtree.
struct node *insert(struct node *node, int key) {
    // 1. Perform the BST insertion
    if (node == NULL) {
        struct node *node = (struct node *) malloc(sizeof(struct node));
        node->key = key;
        node->left = NULL;
        node->right = NULL;
        node->height = 1;  // new node is initially added at leaf
        return node;
    }
    if (key < node->key) node->left = insert(node->left, key);
    else if (key > node->key) node->right = insert(node->right, key);
    else return node;// Equal keys are not allowed in BST
    // 2. Update height of its ancestor
    node->height = 1 + max(height(node->left), height(node->right));
    // 3. Check whether its ancestor node became unbalanced
    int balance = 0;
    if (node != NULL) {
        balance = height(node->left) - height(node->right);
    }
    // unbalanced 4 cases:
    // LL Case
    if (balance > 1 && key < node->left->key)
        return rightRotate(node);
    // RR Case
    if (balance < -1 && key > node->right->key)
        return leftRotate(node);
    // LR Case
    if (balance > 1 && key > node->left->key) {
        node->left = leftRotate(node->left);
        return rightRotate(node);
    }
    // RL Case
    if (balance < -1 && key < node->right->key) {
        node->right = rightRotate(node->right);
        return leftRotate(node);
    }
    // return the (unchanged) node pointer
    return node;
}

删除一个节点

// delete a node with given key from subtree with given root
struct node *deleteNode(struct node *root, int key) {
    // 1. BST deletion
    if (root == NULL) return root;
    // If the key to be deleted is smaller than the
    // root's key, then it lies in left subtree
    if (key < root->key) root->left = deleteNode(root->left, key);
        // If the key to be deleted is greater than the
        // root's key, then it lies in right subtree
    else if (key > root->key) root->right = deleteNode(root->right, key);
        // if key is same as root's key, then This is
        // the node to be deleted
    else {
        // node with only one child or no child
        if ((root->left == NULL) || (root->right == NULL)) {
            struct node *temp = root->left ? root->left : root->right;
            if (temp == NULL) {// No child case
                temp = root;
                root = NULL;
            } else // One child case
                *root = *temp; // Copy the contents of the non-empty child
            free(temp);
        } else {
            // node with two children: Get the inorder
            // successor (smallest in the right subtree)
            struct node *current = root->right;
            // loop down to find the leftmost leaf
            while (current->left != NULL) {
                current = current->left;
            }
            struct node *temp = current;
            // Copy the inorder successor's data to this node
            root->key = temp->key;
            // Delete the inorder successor
            root->right = deleteNode(root->right, temp->key);
        }
    }
    if (root == NULL) return root; // empty
    // 2. Update height of current node
    root->height = 1 + max(height(root->left), height(root->right));
    // 3. Check whether this node became unbalanced
    int balance_root = 0, balance_left = 0, balance_right = 0;
    if (root != NULL) balance_root = height(root->left) - height(root->right);
    if (root->left != NULL) balance_left = height(root->left->left) - height(root->left->right);
    if (root->right != NULL) balance_right = height(root->right->left) - height(root->right->right);
    // unbalanced 4 cases:
    // LL Case
    if (balance_root > 1 && balance_left >= 0)
        return rightRotate(root);
    // LR Case
    if (balance_root > 1 && balance_left < 0) {
        root->left = leftRotate(root->left);
        return rightRotate(root);
    }
    // RR Case
    if (balance_root < -1 && balance_right <= 0)
        return leftRotate(root);
    // RL Case
    if (balance_root < -1 && balance_right > 0) {
        root->right = rightRotate(root->right);
        return leftRotate(root);
    }
    return root;
}

⭐哈夫曼树(HuffmanTree)√

树中结点常常被赋予一个表示某种意义的数值,称为该结点的权。从树根结点到任意结点的路径长度(经过的边数)与该结点上权值的乘积,称为该结点的带权路径长度。树中所有结点的带权路径长度之和称为全树的带权路径长度,记为: WPL = ∑ i = 1 n W i L i \displaystyle{\textit{WPL}=\sum_{i=1}^{n}W_iL_i} WPL=i=1nWiLi ,其中, W i W_i Wi 是第 i i i 个结点所带的权值, L i L_i Li 是该结点到根结点的路径长度。
在含有 n n n 个带权叶的二叉树中,其中带权路径长度 ( W P L ) (WPL) (WPL) 最小的二叉树称为哈夫曼树,也称最优二叉树。

  • 创建哈夫曼树:
    1. 我们把每一个叶子结点,都当做树一颗独立的树(只有根结点的树),这样就形成了一个森林,按照叶子权值小到大压入队列存储;
    2. 借助队列,出队权值最小两个节点,生成他们的父节点,父节点权值为两节点和;
    3. 再出队一个节点,与上一步骤父节点同层级,按照左小右大生成他们的父节点,父节点权值为两节点和;
    4. ⋯ \cdots ⋯ \cdots 按照3.依次完成所有节点。
void Select(HuffmanTree &HT, int n, int &min1, int &min2) {
    int minnum;
    for (int i = 1; i <= n; i++) { //find the minimum
        if (HT[i].parent == 0) {
            minnum = i;
            break;
        }
    }
    for (int i = 1; i <= n; i++) {
        if (HT[i].parent == 0) if (HT[i].weight < HT[minnum].weight) minnum = i;
    }
    min1 = minnum;
    for (int i = 1; i <= n; i++) { //second minimum
        if (HT[i].parent == 0 && i != min1) {
            minnum = i;
            break;
        }
    }
    for (int i = 1; i <= n; i++) {
        if (HT[i].parent == 0 && i != min1) if (HT[i].weight < HT[minnum].weight) minnum = i;
    }
    min2 = minnum;
}

void CreatHuff(HuffmanTree &HT, int *weight, int count) {
    int num, min1, min2;
    num = count * 2 - 1; //the number of nodes
    HT = (HuffmanTree) malloc(sizeof(HTNode) * num); //allocate the memory space
    for (int index = 1; index <= count; index++) { //original sequence
        HT[index].weight = weight[index];
        HT[index].parent = 0;
        HT[index].lchild = 0;
        HT[index].rchild = 0;
    }
    for (int index = count + 1; index <= num; index++) { //initialize
        HT[index].weight = 0;
        HT[index].parent = 0;
        HT[index].lchild = 0;
        HT[index].rchild = 0;
    }
    printf("\nthe HuffmanTree is: \n");
    for (int index = count + 1; index <= num; index++) {
        //find the first two minimum nodes in the sequence of HT[1]~HT[i-1] as min1 min2
        Select(HT, index - 1, min1, min2);
        HT[min1].parent = index; //empty the nodes
        HT[min2].parent = index;
        HT[index].lchild = min1; //set the l/r child as min1/min2
        HT[index].rchild = min2;
        HT[index].weight = HT[min1].weight + HT[min2].weight; //weight of their parent
        printf("%d (%d, %d)\n", HT[index].weight, HT[min1].weight, HT[min2].weight);
    }
    printf("\n");
}

哈夫曼编码???

集合


基础 邻接矩阵、邻接表 √

邻接矩阵(Adjacency Matrix)的存储结构是用一维数组存储图中顶点的信息
e.g.图 G = ( V , E ) G=(V, E) G(V,E) n n n 个确定的顶点,即 V = { v 0 , v 1 , ⋯   , v n − 1 } V=\{v_0, v_1, \cdots, v_{n-1}\} V{v0,v1,,vn1} ,则表示 G G G 中各顶点相邻关系为一个 n × n n \times n n×n 的矩阵,矩阵的元素为 a r c [ i ] [ j ] = { 1 , 若 ( v i , v i ) ∈ E 或 < v i , v i > ∈ E ; 0 , 反 之 arc[i][j]=\begin{cases} 1, 若 (\textit{v}_{i}, \textit{v}_{i}) \in \textit{E} 或 <\textit{v}_{i}, \textit{v}_{i}> \in \textit{E};\\ 0, 反之 \end{cases} arc[i][j]={1,(vi,vi)E<vi,vi>E;0,

v1
v0
V2
v3

邻接表:
node v 0 v 1 v 2 v 3 v 4 v 0 v 1 v 2 v 3 v 4 ∣ 0 1 0 0 1 1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 ∣ \begin{array}{cc} \text{node}\begin{array}{cccc} \textit{v}_0&\textit{v}_1&\textit{v}_2&\textit{v}_3&\textit{v}_4\\ \end{array}\\ \begin{array}{c} \textit{v}_0\\ \textit{v}_1\\ \textit{v}_2\\ \textit{v}_3\\ \textit{v}_4 \end{array} \left|\begin{array}{llcrr} 0&1&0&0&1 \\ 1&0&1&1&1 \\ 0&1&0&1&0 \\ 1&1&0&1&0 \\ \end{array}\right| \end{array} nodev0v1v2v3v4v0v1v2v3v401011011010001111100
针对上示图,建立邻接表:

// A C Program to demonstrate adjacency list
// representation of graphs
#include 
#include 

// A structure to represent an adjacency list node
struct AdjListNode {
    int dest;
    struct AdjListNode *next;
};

// A structure to represent an adjacency list
struct AdjList {
    struct AdjListNode *head;
};

// A structure to represent a graph. A graph
// is an array of adjacency lists.
// Size of array will be V (number of vertices
// in graph)
struct Graph {
    int V;
    struct AdjList *array;
};

// A utility function to create a new adjacency list node
struct AdjListNode *newAdjListNode(int dest) {
    struct AdjListNode *newNode = (struct AdjListNode *) malloc(sizeof(struct AdjListNode));
    newNode->dest = dest;
    newNode->next = NULL;
    return newNode;
}

// A utility function that creates a graph of V vertices
struct Graph *createGraph(int V) {
    struct Graph *graph = (struct Graph *) malloc(sizeof(struct Graph));
    graph->V = V;
    // Create an array of adjacency lists. Size of
    // array will be V
    graph->array = (struct AdjList *) malloc(V * sizeof(struct AdjList));
    // Initialize each adjacency list as empty by
    // making head as NULL
    int i;
    for (i = 0; i < V; ++i) {
        graph->array[i].head = NULL;
    }
    return graph;
}

// Adds an edge to an undirected graph
void addEdge(struct Graph *graph, int src, int dest) {
    // Add an edge from src to dest. A new node is
    // added to the adjacency list of src. The node
    // is added at the beginning
    struct AdjListNode *check = NULL;
    struct AdjListNode *newNode = newAdjListNode(dest);
    if (graph->array[src].head == NULL) {
        newNode->next = graph->array[src].head;
        graph->array[src].head = newNode;
    } else {
        check = graph->array[src].head;
        while (check->next != NULL) {
            check = check->next;
        }
        //graph->array[src].head = newNode;
        check->next = newNode;
    }
    // Since graph is undirected, add an edge from
    // dest to src also
    newNode = newAdjListNode(src);
    if (graph->array[dest].head == NULL) {
        newNode->next = graph->array[dest].head;
        graph->array[dest].head = newNode;
    } else {
        check = graph->array[dest].head;
        while (check->next != NULL) {
            check = check->next;
        }
        check->next = newNode;
    }
    //newNode = newAdjListNode(src);
    //newNode->next = graph->array[dest].head;
    //graph->array[dest].head = newNode;
}

BFS DFS

最小生成树 (Minimum Spanning Tree) √

    1. no cycle
    2. all connected
    3. there’s N − 1 N-1 N1 edges for N N N vertices
  • 最小
    minimum weight

构建方法:

  1. Kruskal,直接出队最小权值边;
  2. Prim,选择两顶点边集合的最小边.

Kruskal √

  1. 取出所有节点,边放入对应队列中
  2. sort it
  3. 按序出队,回帖边,并判断是否会形成环,无环则保留(并查集)
int Find(int n) {
	while(n!=pre[n])
		n=pre[n];
	return n;
}//寻找祖宗节点
void Merge_set(LINE node) {
	int i=Find(node.mmp);
	int j=Find(node.mvp);
	if(i!=j) {
		pre[i]=j;
		coun++;	//记录路数
		sun+=node.weight;
	}
}//这条弧入集
  1. 边数达到 N − 1 N-1 N1,完成

Prim √

  1. 记录所有节点的数据:
    node selected minDist parent 0 1 2 3 4 5 6 7 8 F F F F F F F F F ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ − 1 − 1 − 1 − 1 − 1 − 1 − 1 − 1 − 1 \begin{array}{cc} \begin{array}{r} \text{node}\\\text{selected}\\\text{minDist}\\\text{parent} \end{array}& \begin{array}{l} 0&1&2&3&4&5&6&7&8\\ F&F&F&F&F&F&F&F&F\\ \infty & \infty & \infty & \infty & \infty & \infty & \infty & \infty & \infty\\ -1 & -1 & -1 & -1 & -1 & -1 & -1 & -1 & -1 \end{array} \end{array} nodeselectedminDistparent0F11F12F13F14F15F16F17F18F1

node:节点序号
selected:记录是否被加入MST中,初始为False
minDist:与当前操作的节点的最小路径,初始为 ∞ \infty
parent:该节点最后相连的父节点,初始为-1

  1. 任选图 G G G 中的一点作为起始点 a a a;
  2. a a a 点加入MST,作为root;
  3. 遍历点 a a a 的所有相连点,并修改对应节点 selected,minDist,parent 参数,选择最小 minDist 的对应节点 b b b 作为 a a a 的孩子纳入MST;
  4. 遍历点 b b b ⋯ \cdots ⋯ \cdots (重复第4.步骤),检查 parent 参数不为 b b b 的节点纳入MST,父节点为对应的 parent 参数;
  5. selected 全为True,构建MST完成.

Prim 和 Kruskal 比较 √

记顶点数 v v v,边数 e e e

  • Prim: 时间复杂度为 O ( v 2 ) O(v^2) O(v2)。即是点相关算法,该算法适合于稠密图
  • Kruskal: 需要对边进行访问,所以Kruskal算法的时间复杂度只与边有关系,即是边相关算法,可以证明其时间复杂度为 O ( e log e ) O(e \text{log} e) O(eloge)。适合稀疏图

最短路径 √

Dijkstra算法 √

90 % 90\% 90% 等同于Prim算法
时间复杂度: O ( e log v ) O(e \text{log} v) O(elogv)

  1. 将 minDist 变更为出发节点到对应节点的距离和;parent变更为上一节点;
  2. 每次计算相邻节点加和后的距离,若加和后的数值小于原来的距离和,跟新距离以及对应的上一节点;
  3. 固定现有的为固定节点中距离和最小的节点;
  4. 循环3.得到所有点到出发点的最小距离.

Floyd算法 √

应用于求任意两个顶点的最小路径,时间复杂度: O ( v 3 ) O(v^3) O(v3)
e.g. P − 1  0   1   2   3  0 1 2 3 0 5 ∞ 10 ∞ 0 3 ∞ ∞ ∞ 0 1 ∞ ∞ ∞ 0 \begin{array}{cc} \text{P}_{-1} \begin{array}{cccc} \text{ 0 }&\text{ 1 }&\text{ 2 }&\text{ 3 }\\ \end{array}\\ \begin{array}{c} 0\\1\\2\\3 \end{array} \boxed{\begin{array}{llcrr} 0 & 5 & \infin & 10\\ \infin & 0 & 3 & \infin\\ \infin & \infin & 0 & 1\\ \infin & \infin & \infin & 0\\ \end{array}} \end{array} P1 0  1  2  3 0123050301010,这是迭代前的两点直接连接的距离,类似于邻接矩阵。

10
5
3
1
0
1
2
3

思路:初始化解矩阵等于输入的图矩阵。依次将每一个顶点视为中间节点来更新所有最短路径。

  1. 依次将节点 { 0 , 1 , 2 , ⋯   , k − 1 } \{0, 1, 2, \cdots, k-1\} {0,1,2,,k1} 视为中间节点。对于每对 ( i , j ) (i, j) (i,j) 的初末节点,分别有两种可能的情况:
  2.  if(k 不是从 i 到 j 的最短路径中的中间顶点){
        保持 dist[i][j] 的值不变;
     }
     else if(k 是从 i 到 j 的最短路径中的中间顶点){
         if(dist[i][j] > dist[i][k] + dist[k][j]){
             (dist[i][j] = dist[i][k] + dist[k][j];
         }
     }
    

拓扑排序

也就是最大长度的路径计算
一个很傻的简单方法 DFS(无向图
时间复杂度: O ( v ⋅ ( v + e ) ) O(v \cdot (v + e)) O(v(v+e))
设定max_length记录DFS过程中的最大边长和,当访问了所有节点后的length_sum大于max_length,则更新max_length当前值。

AOV网 √

一些概念:

  • DAG (directed acycline graph):有向无环图;使用并查集可以判断是否为无环图
  • AOV网 (Activity On Vertex Network):在有向图中,用顶点表示活动,用边及其边长表示活动之间的优先关系。AOV网不应该出现环,即不存在某项活动是以自己为先决条件的

在一个有向图中,对所有的节点进行排序,要求没有一个节点指向它前面的节点(即入度为0)
1. 统计所有节点的入度
2. 分离出入度为0的节点,把该节点指向的节点的入度减一
3. 循环2.直到所有的节点都被分离出来
4. 如果最后不存在入度为0的节点,说明有环—>不存在拓扑排序—>无解

AOE网 √

一些概念:

  • AOE网 (activity on edge network):以边表示活动,顶点表示事件的网。是一个带权、有向、无环图。权值可表示活动持续的时间/成本等
  • 关键路径(critical path):从开始点到完成点的最长路径长度,表示了完整完成活动的时间/成本总量
    e.g.
a1=6
a4=1
a7=9
a10=2
a2=4
a5=1
a8=7
a11=4
a3=5
a6=2
a9=4
v0
v1
v2
v3
v4
v5
v6
v7
v8
  1. 由已知的图可以得到网中的事件最早于最迟时间和活动最早最迟时间:
event(node) v0 v1 v2 v3 v4 v5 v6 v7 v8
v e a r l y v_{early} vearly 0 6 4 5 7 7 16 14 18
v l a t e v_{late} vlate 0 6 6 8 7 10 16 14 18
acti(edge) a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11
e e e 0 0 0 6 4 5 7 7 7 16 14
l l l 0 2 3 6 6 8 7 7 10 16 14
  1. 由上表中 v e a r l y v_{early} vearly v l a t e v_{late} vlate 相等的节点所代表的事件即构成关键路径,包括: v 0 → v 1 → v 4 → v 6 → v 8 v_0 \to v_1 \to v_4 \to v_6 \to v_8 v0v1v4v6v8 v 0 → v 1 → v 4 → v 7 → v 8 v_0 \to v_1 \to v_4 \to v_7 \to v_8 v0v1v4v7v8
  2. 由上表中 e e e l l l 相等的活动,得到关键活动: a 1 → a 4 → a 7 → a 10 a_1 \to a_4 \to a_7 \to a_{10} a1a4a7a10 a 1 → a 4 → a 8 → a 11 a_1 \to a_4 \to a_8 \to a_{11} a1a4a8a11

散列表??


排序 √

选择 √

选择排序(Selection Sort) √

每次从待排序列中选出一个最小(大)值,然后放在序列的起始位置,直到全部待排数据排完即可。p.s.其实也可以同时搞定min,max

时间: O ( N 2 ) O(\text{N}^2) O(N2); 空间: O ( 1 ) O(1) O(1)

void SelectSort(int *arr, int count) {
    int begin = 0, end = count - 1;
    while (begin < end) {
        int max_index = begin; //保存最大值的下标
        int min_index = begin; //保存最小值的下标
        for (int index = begin; index <= end; index++) { //找出最大值和最小值的下标
            if (arr[index] < arr[min_index]) {
                min_index = index;
            }
            if (arr[index] > arr[max_index]) {
                max_index = index;
            }
        }
        swap(&arr[min_index], &arr[begin]); //最小值放在序列开头
        if (begin == max_index) { //防止最大的数在begin位置被换走
            max_index = min_index;
        }
        swap(&arr[max_index], &arr[end]); //最大值放在序列结尾
        begin++; end--; //向中间收拢待排序列
    }
}

堆排序(Heap Sort) √

选出左右孩子中小的哪一个,跟父亲交换,小的往上浮,大的往下沉,如果要建大堆则相反。

建堆时间: O ( N ) O(\text{N}) O(N);
向下调整算法的时间: O ( log 2 N ) O(\textit{log}_{2}\text{N}) O(log2N);
堆排序的时间: O ( N ⋅ log 2 N ) O(\text{N}\cdot\textit{log}_{2}\text{N}) O(Nlog2N)

//堆向下调整算法
//建小堆
void AdjustDown(int *heap, int count, int root) {
    int parentI = root;
    int childI = parentI * 2 + 1;
    while (childI < count) { //孩子在数组下标范围内
        if (heap[childI + 1] < heap[childI] && childI + 1 < count) {//防止没有右孩子
            ++childI;
        }
        if (heap[childI] < heap[parentI]) { //小的往上浮,大的往下浮
            int temp = heap[parentI];
            heap[parentI] = heap[childI];
            heap[childI] = temp;
            parentI = childI;
            childI = parentI * 2 + 1;
        } else { //中途child>parent则已满足小堆,直接break
            break;
        }
    }
}

//堆的向上调整算法,插入新数据时使用
void AdjustUp(int *heap, int childI) {
    int parentI = (childI - 1) / 2;
    while (childI > 0) {
        if (heap[childI] < heap[parentI]) {
            int temp = heap[parentI];
            heap[parentI] = heap[childI];
            heap[childI] = temp;
            childI = parentI;
            parentI = (childI - 1) / 2;
        } else {
            break;
        }
    }
}

//降序
void HeapSort(int *heap, int count) {
    for (int index = (count - 1 - 1) / 2; index >= 0; --index) {
        AdjustDown(heap, count, index);
    }
    int end = count - 1;
    //把最小的换到最后一个位置,不把最后一个数看作堆里的
    //每次选出剩下数中最小的
    //从后往前放
    while (end > 0) {
        int temp = heap[end];
        heap[end] = heap[0];
        heap[0] = temp;
        //选出次小的数
        AdjustDown(heap, end, 0);
        end--;
    }
}

选择 √

插入排序(Insertion Sort) √

由num[i]开始,取temp=num[i],i由0开始往后移动直至n-1;
temp=num[i]由num[i-1]开始往前扫描,直至number[0],若temp更小(大)则交换,非则插入不动。最后得到升(降)序表。

时间 O ( N 2 ) O(\text{N}^{2}) O(N2); 空间 O ( 1 ) O(1) O(1)

void InsertSort(int *arr, int count) {
    for (int index = 0; index < count - 1; index++) {
        int end = index; //记录有序序列最后一个元素的下标
        int tem = arr[end + 1]; //待插入的元素
        while (end >= 0) { //单趟排
            if (tem < arr[end]) { //比插入的数大就向后移
                arr[end + 1] = arr[end];
                end--;
            } else { //比插入的数小,跳出循环
                break;
            }
        }
        arr[end + 1] = tem; //tem放到比插入的数小的数的后面
    }
}

希尔排序(Shell Sort) √

分组进行预排序,使得排序接近有序,再进行插入排序。分组依次为 n 2 i \frac{n}{2^i} 2in 个。

时间: O ( N 1.5 ) O(\text{N}^{1.5}) O(N1.5); 空间: O ( 1 ) O(1) O(1)

void ShellSort(int *arr, int count) {
    int gap = count;
    while (gap > 1) {
        gap = gap / 2; //每次对gap折半操作
        for (int index = 0; index < count - gap; index++) { //单趟排序
            int end = index;
            int temp = arr[end + gap];
            while (end >= 0) { //依次对比
                if (temp < arr[end]) {
                    arr[end + gap] = arr[end];
                    end -= gap;
                } else {
                    break;
                }
            }
            arr[end + gap] = temp; //插入数据
        }
    }
}

交换 √

冒泡排序(Bubble Sort) √

左边大于右边交换一趟排下来最大的在右边

时间: O ( N 2 ) O(\text{N}^{2}) O(N2); 空间: O ( 1 ) O(1) O(1)

快速排序(Quick Sort) √

/*Partition method which selects a pivot
  and places each element which is less than the pivot value to its left
  and the elements greater than the pivot value to its right
  arr[] --- array to be partitioned
  lower --- lower index
  upper --- upper index
*/
int partition(int arr[], int lower, int upper) {
    int i = (lower - 1);
    int pivot = arr[upper];  // Selects last element as the pivot value
    int j;
    for (j = lower; j < upper; j++) {
        if (arr[j] <= pivot) {  // if current element is smaller than the pivot
            i++;  // increment the index of smaller element
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[upper]);
    //places the last element i.e, the pivot to its correct position
    return (i + 1);
}

/*This is where the sorting of the array takes place
    arr[] --- Array to be sorted
    lower --- Starting index
    upper --- Ending index
*/
void quickSort(int arr[], int lower, int upper) {
    if (upper > lower) {
        // partitioning index is returned by the partition method , partition
        // element is at its correct poition
        int partitionIndex = partition(arr, lower, upper);
        // Sorting elements before and after the partition index
        quickSort(arr, lower, partitionIndex - 1);
        quickSort(arr, partitionIndex + 1, upper);
    }
}

你可能感兴趣的:(notes,of,期末,数据结构,c语言,算法,排序算法,b树)