表、栈、队列
表
一般指的是链表,可以在任意节点进行插入活删除操作
栈
后进先出就是栈
栈是限定仅在**表尾(栈顶)**进行插入和删除操作的线性表
栈顶进栈出栈,栈底不能进行插入删除操作
栈的抽象数据类型
ADT 栈(stack ) Date 和线性表相同。元素有相同的数据类型,相邻元素有前驱和后继的关系 Operation InitStack (*S) : 初始化操作,建立一个空栈S。 DestoryStack(*S):如果栈存在则销毁它。 ClearStack (*S) : 将栈清空。 StackEmpty(*S):如果栈为空则返回true ,否则返回false 。 GetTop(*S,*e):如果栈存在且非空,用e返回S的栈顶储存的值。 Push (*S, e) :若栈S存在,插入新的元素e到栈S中并成为栈顶元素。 Pop (*S,*e) :删除S中栈顶元素,并用e返回其值。 StackLength (S) :返回栈S的元素个数。 endADT
顺序栈
栈的顺序储存结构
#define MAXSIZE 50 typedef int ElemType; typedef struct { ElemType data[MAXSIZE]; int top; }SqStack;
顺序栈操作
void InitStack (SqStack *S) { S->top = -1 ; }
bool StackEmpty (SqStack S) { if (S.top == -1 ){ return true ; } else { return false ; } }
Status Push (SqStack *S, ElemType e) { if (S->top == MAXSIZE-1 ){ return ERROR; } S->top++; S->data[S->top] = e; return OK; }
Status Pop (SqStack *S, ElemType *e) { if (S->top == -1 ){ return ERROR; } *e = S->data[S->top]; S->top--; return OK; }
栈的链式结构
typedef struct StackNode { ElemType data; StackNode *next; }StackNode, *LinkStackPtr; typedef struct LinkStack { LinkStackPtr top; int count = 0 ; }LinkStack; LinkStackPtr top; int count;
int Push (LinkStack *P, ElemType e) { LinkStackPtr s = (LinkStackPtr)malloc (sizeof (StackNOde)); s->data = e; s->next = P->top; P->top = s; P->count++; return OK; }
int Pop (LinkStack *P, ElemType *e) { LinkStackPtr temp; if (P->count == 0 ){ return ERROR; } else { *e = P->top->data; temp = P->top; P->top = P->top->next; free (temp); P->count--; } return OK }
队列
先进先出就是队列
普通队列
懒得细化了全丢代码了
#include <stdio.h> #include <stdbool.h> #define MAX_SIZE 100 int queue [MAX_SIZE]; int front = -1 ; int rear = -1 ; bool isEmpty () { return front == -1 && rear == -1 ; } bool isFull () { return (rear + 1 ) % MAX_SIZE == front; } int size () { if (isEmpty()) { return 0 ; } return (MAX_SIZE - front + rear + 1 ) % MAX_SIZE; } void enqueue (int data) { if (isFull()) { printf ("队列已满,无法入队\n" ); return ; } if (isEmpty()) { front = rear = 0 ; } else { rear = (rear + 1 ) % MAX_SIZE; } queue [rear] = data; } int dequeue () { if (isEmpty()) { printf ("队列为空,无法出队\n" ); return -1 ; } int data = queue [front]; if (front == rear) { front = rear = -1 ; } else { front = (front + 1 ) % MAX_SIZE; } return data; } int main () { enqueue(1 ); enqueue(2 ); enqueue(3 ); printf ("队头元素:%d\n" , queue [front]); printf ("队列大小:%d\n" , size()); printf ("出队:%d\n" , dequeue()); printf ("出队:%d\n" , dequeue()); printf ("队列是否为空:%s\n" , isEmpty() ? "是" : "否" ); return 0 ; }
双端队列
就是有两个端的队列,每个端的首尾都能进行插入删除操作
#include <stdio.h> #include <stdbool.h> #define MAX_SIZE 100 int deque [MAX_SIZE]; int front = -1 ; int rear = -1 ; bool isEmpty () { return front == -1 && rear == -1 ; } bool isFull () { return (front == 0 && rear == MAX_SIZE - 1 ) || (rear == front - 1 ); } int size () { if (isEmpty()) { return 0 ; } if (front <= rear) { return rear - front + 1 ; } else { return (MAX_SIZE - front) + (rear + 1 ); } } void enqueueFront (int data) { if (isFull()) { printf ("双端队列已满,无法前端入队\n" ); return ; } if (isEmpty()) { front = rear = 0 ; } else if (front == 0 ) { front = MAX_SIZE - 1 ; } else { front--; } deque [front] = data; } void enqueueRear (int data) { if (isFull()) { printf ("双端队列已满,无法后端入队\n" ); return ; } if (isEmpty()) { front = rear = 0 ; } else if (rear == MAX_SIZE - 1 ) { rear = 0 ; } else { rear++; } deque [rear] = data; } int dequeueFront () { if (isEmpty()) { printf ("双端队列为空,无法前端出队\n" ); return -1 ; } int data = deque [front]; if (front == rear) { front = rear = -1 ; } else if (front == MAX_SIZE - 1 ) { front = 0 ; } else { front++; } return data; } int dequeueRear () { if (isEmpty()) { printf ("双端队列为空,无法后端出队\n" ); return -1 ; } int data = deque [rear]; if (front == rear) { front = rear = -1 ; } else if (rear == 0 ) { rear = MAX_SIZE - 1 ; } else { rear--; } return data; } int main () { enqueueFront(1 ); enqueueRear(2 ); enqueueFront(3 ); printf ("前端出队:%d\n" , dequeueFront()); printf ("后端出队:%d\n" , dequeueRear()); printf ("双端队列大小:%d\n" , size()); printf ("双端队列是否为空:%s\n" , isEmpty() ? "是" : "否" ); return 0 ; }
树
遍历
前序遍历:根节点->左节点->右节点
中序遍历:左节点->根节点->右节点
后序遍历:左节点->右节点->根节点
二叉树
struct TreeNode { int data; struct TreeNode * left ; struct TreeNode * right ; }; struct TreeNode* createNode (int data) { struct TreeNode * newNode = (struct TreeNode*)malloc (sizeof (struct TreeNode)); if (newNode == NULL ) { printf ("内存分配失败\n" ); exit (1 ); } newNode->data = data; newNode->left = newNode->right = NULL ; return newNode; } struct TreeNode* insert (struct TreeNode* root, int data) { if (root == NULL ) { return createNode(data); } if (data < root->data) { root->left = insert(root->left, data); } else if (data > root->data) { root->right = insert(root->right, data); } return root; } struct TreeNode* findMin (struct TreeNode* root) { while (root->left != NULL ) { root = root->left; } return root; } struct TreeNode* deleteNode (struct TreeNode* root, int data) { if (root == NULL ) { return root; } if (data < root->data) { root->left = deleteNode(root->left, data); } else if (data > root->data) { root->right = deleteNode(root->right, data); } else { if (root->left == NULL ) { struct TreeNode* temp = root->right; free (root); return temp; } else if (root->right == NULL ) { struct TreeNode* temp = root->left; free (root); return temp; } struct TreeNode * temp = findMin(root->right); root->data = temp->data; root->right = deleteNode(root->right, temp->data); } return root; } void inOrderTraversal (struct TreeNode* root) { if (root != NULL ) { inOrderTraversal(root->left); printf ("%d " , root->data); inOrderTraversal(root->right); } }
哈夫曼树
struct HuffmanNode { char data; int frequency; struct HuffmanNode * left ; struct HuffmanNode * right ; }; struct HuffmanNode* createNode (char data, int frequency) { struct HuffmanNode * newNode = (struct HuffmanNode*)malloc (sizeof (struct HuffmanNode)); if (newNode == NULL ) { printf ("内存分配失败\n" ); exit (1 ); } newNode->data = data; newNode->frequency = frequency; newNode->left = newNode->right = NULL ; return newNode; } struct HuffmanNode* buildHuffmanTree (struct HuffmanNode* nodes[], int n) { while (n > 1 ) { int min1 = 0 , min2 = 1 ; if (nodes[min1]->frequency > nodes[min2]->frequency) { int temp = min1; min1 = min2; min2 = temp; } for (int i = 2 ; i < n; i++) { if (nodes[i]->frequency < nodes[min1]->frequency) { min2 = min1; min1 = i; } else if (nodes[i]->frequency < nodes[min2]->frequency) { min2 = i; } } struct HuffmanNode * parent = createNode('$' , nodes[min1]->frequency + nodes[min2]->frequency); parent->left = nodes[min1]; parent->right = nodes[min2]; nodes[min1] = parent; nodes[min2] = nodes[n - 1 ]; n--; } return nodes[0 ]; } void huffmanEncode (struct HuffmanNode* root, int arr[], int top) { if (root->left) { arr[top] = 0 ; huffmanEncode(root->left, arr, top + 1 ); } if (root->right) { arr[top] = 1 ; huffmanEncode(root->right, arr, top + 1 ); } if (!root->left && !root->right) { printf ("字符: %c, 编码: " , root->data); for (int i = 0 ; i < top; i++) { printf ("%d" , arr[i]); } printf ("\n" ); } } void huffmanDecode (struct HuffmanNode* root, int index, char encodedStr[]) { if (root == NULL ) { return ; } if (!root->left && !root->right) { printf ("%c" , root->data); huffmanDecode(root, index, encodedStr); } else { index++; if (encodedStr[index] == '0' ) { huffmanDecode(root->left, index, encodedStr); } else { huffmanDecode(root->right, index, encodedStr); } } } void destroyHuffmanTree (struct HuffmanNode* root) { if (root) { destroyHuffmanTree(root->left); destroyHuffmanTree(root->right); free (root); } }
AVL树
在AVL树中,每个节点有以下特性:
平衡因子 (Balance Factor):每个节点的平衡因子是其左子树的高度减去其右子树的高度。在AVL树中,每个节点的平衡因子必须是-1、0或1。
自平衡:如果在插入或删除节点后,某个节点的平衡因子变得不在-1、0或1的范围内,那么就需要通过旋转来重新平衡树。常见的旋转操作有:
右旋 (Right Rotation):当节点的左子树比右子树高两个单位时进行,特别是在左子节点的左子树添加了一个新节点时。
左旋 (Left Rotation):当节点的右子树比左子树高两个单位时进行,特别是在右子节点的右子树添加了一个新节点时。
左-右旋(Left-Right Rotation):当在一个节点的左子节点的右子树添加一个新节点时进行,这是一种双旋转,先对左子节点进行左旋,然后对原节点进行右旋。
右-左旋(Right-Left Rotation):当在一个节点的右子节点的左子树添加一个新节点时进行,这也是一种双旋转,先对右子节点进行右旋,然后对原节点进行左旋。
AVL树的操作:
插入(Insertion) :插入新节点后,沿着路径向上回溯并检查每个节点的平衡因子。如果发现平衡因子不在-1、0或1的范围内,执行适当的旋转操作来重新平衡树。
删除(Deletion) :删除节点后,也需要回溯并检查平衡因子,采取相应的旋转操作来维持树的平衡。
搜索(Search) :由于AVL树是二叉搜索树,所以搜索操作与普通的二叉搜索树相同,遵循二分查找的原则。
#include <stdio.h> #include <stdlib.h> struct TreeNode { int data; struct TreeNode * left ; struct TreeNode * right ; int height; };
AVL平衡因子
int height (struct TreeNode* node) { if (node == NULL ) { return 0 ; } return node->height; } int getBalance (struct TreeNode* node) { if (node == NULL ) { return 0 ; } return height(node->left) - height(node->right); }
AVL创建新节点
struct TreeNode* createNode (int data) { struct TreeNode * newNode = (struct TreeNode*)malloc (sizeof (struct TreeNode)); if (newNode == NULL ) { printf ("内存分配失败\n" ); exit (1 ); } newNode->data = data; newNode->left = newNode->right = NULL ; newNode->height = 1 ; return newNode; }
右旋
struct TreeNode* rotateRight (struct TreeNode* y) { struct TreeNode * x = y->left; struct TreeNode * T2 = x->right; x->right = y; y->left = T2; y->height = 1 + (height(y->left) > height(y->right) ? height(y->left) : height(y->right)); x->height = 1 + (height(x->left) > height(x->right) ? height(x->left) : height(x->right)); return x; }
左旋
struct TreeNode* rotateLeft (struct TreeNode* x) { struct TreeNode * y = x->right; struct TreeNode * T2 = y->left; y->left = x; x->right = T2; x->height = 1 + (height(x->left) > height(x->right) ? height(x->left) : height(x->right)); y->height = 1 + (height(y->left) > height(y->right) ? height(y->left) : height(y->right)); return y; }
AVL插入节点
struct TreeNode* insert (struct TreeNode* root, int data) { if (root == NULL ) { return createNode(data); } if (data < root->data) { root->left = insert(root->left, data); } else if (data > root->data) { root->right = insert(root->right, data); } else { return root; } root->height = 1 + (height(root->left) > height(root->right) ? height(root->left) : height(root->right)); int balance = getBalance(root); if (balance > 1 && data < root->left->data) { return rotateRight(root); } if (balance < -1 && data > root->right->data) { return rotateLeft(root); } if (balance > 1 && data > root->left->data) { root->left = rotateLeft(root->left); return rotateRight(root); } if (balance < -1 && data < root->right->data) { root->right = rotateRight(root->right); return rotateLeft(root); } return root; }
AVL找到最小值
struct TreeNode* findMin (struct TreeNode* root) { while (root->left != NULL ) { root = root->left; } return root; }
AVL删除节点
struct TreeNode* deleteNode (struct TreeNode* root, int data) { if (root == NULL ) { return root; } if (data < root->data) { root->left = deleteNode(root->left, data); } else if (data > root->data) { root->right = deleteNode(root->right, data); } else { if (root->left == NULL || root->right == NULL ) { struct TreeNode* temp = (root->left) ? root->left : root->right; if (temp == NULL ) { temp = root; root = NULL ; } else { *root = *temp; } free (temp); } else { struct TreeNode* temp = findMin(root->right); root->data = temp->data; root->right = deleteNode(root->right, temp->data); } } if (root == NULL ) { return root; } root->height = 1 + (height(root->left) > height(root->right) ? height(root->left) : height(root->right)); int balance = getBalance(root); if (balance > 1 && getBalance(root->left) >= 0 ) { return rotateRight(root); } if (balance > 1 && getBalance(root->left) < 0 ) { root->left = rotateLeft(root->left); return rotateRight(root); } if (balance < -1 && getBalance(root->right) <= 0 ) { return rotateLeft(root); } if (balance < -1 && getBalance(root->right) > 0 ) { root->right = rotateRight(root->right); return rotateLeft(root); } return root; }
B树
优先队列(堆)
二叉堆
二叉堆是一种完全二叉树,可以被分为两种类型:最大堆和最小堆。
最大堆:在这种堆中,父节点的值总是大于或等于其子节点的值。这意味着堆的最大元素总是位于根节点。
最小堆:相反地,在最小堆中,父节点的值总是小于或等于其子节点的值。这样,堆的最小元素总是位于根节点。
二叉堆的主要操作包括:
插入(Insert):新元素被添加到树的最后一个位置,即完全二叉树的最后一个节点。然后,这个新元素会上浮到合适的位置,以保持堆的性质。这个过程被称为上浮(或堆化向上)。
删除最大(或最小)元素(Delete Max/Min):在最大堆中,删除最大元素意味着移除根节点。为了填补根节点的位置,通常将堆中最后一个元素移动到根位置,然后进行下沉操作,以恢复堆的性质。这个过程被称为下沉(或堆化向下)。
二叉堆通常使用数组来实现,因为它是一种完全二叉树,所以可以高效地利用数组空间。
两种构建方式如下
void insertHeap (int heap[], int *size, int value) { int i; heap[*size] = value; i = *size; (*size)++; while (i != 0 && heap[(i - 1 ) / 2 ] > heap[i]) { int temp = heap[(i - 1 ) / 2 ]; heap[(i - 1 ) / 2 ] = heap[i]; heap[i] = temp; i = (i - 1 ) / 2 ; } } void buildHeap (int arr[], int n) { int startIdx = (n / 2 ) - 1 ; for (int i = startIdx; i >= 0 ; i--) { heapify(arr, n, i); } } void heapify (int arr[], int n, int i) { int smallest = i; int l = 2 * i + 1 ; int r = 2 * i + 2 ; if (l < n && arr[l] < arr[smallest]) smallest = l; if (r < n && arr[r] < arr[smallest]) smallest = r; if (smallest != i) { int temp = arr[i]; arr[i] = arr[smallest]; arr[smallest] = temp; heapify(arr, n, smallest); } }
d-堆
d-堆是一种扩展了的二叉堆,它是一种优先队列的数据结构。在标准的二叉堆中,每个节点最多有两个子节点。而在d-堆中,每个节点最多有d个子节点。这里的d是一个大于或等于2的整数,它表示堆中每个节点的最大子节点数目。
d-堆的主要操作包括:
插入(Insert): 插入一个新元素通常是在堆的最后添加这个元素,然后将它上浮(Upheap)到合适的位置,以保持堆的性质。上浮的过程是将这个新节点与其父节点比较,并在必要时交换它们的位置,这一过程重复进行直到恢复堆的性质。
删除最小(或最大)元素(Delete Min/Max): 在d-堆中删除最小(或最大)元素通常涉及移除根节点,然后将堆的最后一个元素移动到根位置,并进行下沉(Downheap)操作,以保持堆的性质。下沉操作是将节点与其子节点进行比较,并在必要时与最小(或最大)的子节点交换位置,这一过程重复进行直到恢复堆的性质。
左式堆
左式堆是一种优先队列的数据结构,它是二叉堆的一个变种。左式堆的特点在于它的左偏性质,这意味着对于左式堆中的任意节点,其左子树的零路径长度(null path length,即到达一个空链接的最短路径)至少和右子树的零路径长度一样长。
左式堆的主要操作包括:
合并(Merge): 这是左式堆的基本操作。当合并两个左式堆时,会比较两个堆的根节点,并将值较大(或较小,取决于是最大堆还是最小堆)的堆的右子树与另一个堆合并。然后,如果需要,交换合并后的堆的左右子树以保持左偏性质。这个过程是递归的。
删除最小(或最大)元素(Delete Min/Max): 删除堆中的最小(或最大)元素是通过删除根节点,并将其左右子树合并成一个新堆来实现的。
左式堆的一个重要特性是它能够高效地合并两个堆,这使得它在需要频繁合并堆的场景下非常有用。左式堆的操作一般都能在对数时间内完成,这使得它成为实现优先队列的一个很好的选择
二项队列
二项树
二项队列的基础是二项树。一个阶数为k的二项树(记为Bk)具有以下特性:
结构:它是递归定义的。B0是只包含一个节点的树。Bk树有一个根节点,这个根节点有k个子节点,这些子节点分别是B(k-1), B(k-2), …, B0。
节点数:一个Bk树正好有2^k个节点。
高度:Bk树的高度是k。
二项队列
一个二项队列可以看作是二项树的集合,其中每个二项树遵循最小堆性质(或最大堆性质,取决于具体实现),即父节点的键值小于或等于其子节点的键值。在任何时刻,一个二项队列中不能有两个具有相同阶数的二项树。
二项队列的操作
插入(Insert):插入一个新元素等价于合并一个只包含这个元素的二项队列。这通常通过一系列的树合并操作完成。
删除最小元素(Delete Min):由于每个二项树遵循最小堆性质,最小元素总是在根节点中。删除最小元素涉及找到具有最小根的二项树,删除这个根节点,然后将其子树合并回队列中。
合并(Merge):合并两个二项队列是通过合并具有相同阶数的二项树并按照需要进行进位来完成的。
二项队列的优点
高效的合并操作:二项队列可以在对数时间内完成合并,这在某些应用中是非常有用的,特别是在需要频繁合并优先队列的场景中。
其他操作也相对高效:如插入和删除最小元素等操作也能在对数时间内完成。
排序算法
插入排序
void insertionSort (int arr[], int n) { int i, key, j; for (i = 1 ; i < n; i++) { key = arr[i]; j = i - 1 ; while (j >= 0 && arr[j] > key) { arr[j + 1 ] = arr[j]; j = j - 1 ; } arr[j + 1 ] = key; } }
选择排序
void selectionSort (int arr[], int n) { int i, j, min_idx; for (i = 0 ; i < n-1 ; i++) { min_idx = i; for (j = i+1 ; j < n; j++) if (arr[j] < arr[min_idx]) min_idx = j; swap(&arr[min_idx], &arr[i]); } }
希尔排序
直接上代码
void shellSort (int arr[], int n) { for (int gap = n / 2 ; gap > 0 ; gap /= 2 ) { for (int i = gap; i < n; i += 1 ) { int temp = arr[i]; int j; for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) arr[j] = arr[j - gap]; arr[j] = temp; } } }
快速排序
int partition (int arr[], int low, int high) { int pivot = arr[high]; int i = (low - 1 ); for (int j = low; j <= high - 1 ; j++) { if (arr[j] <= pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1 ], &arr[high]); return (i + 1 ); } void quickSort (int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1 ); quickSort(arr, pi + 1 , high); } }
前缀表达式转后缀表达式
中缀表达式转换成后缀表达式(也称为逆波兰表示法)涉及到运算符的优先级和括号的使用。转换规则如下:
初始化两个栈:运算符栈 s1 用于临时存储运算符,包括括号;后缀表达式栈 s2 用于存储最终的后缀表达式。
从左到右扫描中缀表达式。
遇到操作数:直接将其压入 s2。
遇到左括号 (:将其压入 s1。
遇到右括号 ): 依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃。
遇到运算符:
比较其与 s1 栈顶运算符的优先级:
如果 s1 为空,或栈顶运算符为左括号 (,则直接将此运算符入栈;
否则,若优先级比栈顶运算符的高,也将运算符压入 s1;
如果优先级低于或等于栈顶运算符,则将 s1 栈顶的运算符弹出并压入到 s2 中,然后再次转到(6)与 s1 中新的栈顶运算符相比较。
将 s1 中剩余的运算符依次弹出并压入 s2。
依次弹出 s2 中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
查缺补漏