设需要排序的7个初始关键字是:39,38,65,97,76,13,27。写出分别采用直接插入排序和快速排序的每一步过程。
直接插入排序
直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
第一趟
39(已排序)
38与39比较,38小,交换位置:38,39(已排序)
65与39比较,65大,不交换:38,39,65(已排序)
97与65比较,97大,不交换:38,39,65,97(已排序)
76与65比较,76大,不交换:38,39,65,97,76(已排序)
13与39比较,13小,交换位置:13,38,39,65,97,76(已排序)
27与38比较,27小,交换位置:27,13,38,39,65,97,76(已排序)
结果:27,13,38,39,65,76,97
快速排序
快速排序的基本思想是:通过一次排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分继续进行排序。
选择基准值:这里我们选择第一个元素39作为基准值。
分区:将比基准值小的元素放在它的左边,比基准值大的元素放在它的右边。
39(基准值)
38比39小,放在左边:38,39
65比39大,放在右边:38,39,65
97比39大,放在右边:38,39,65,97
76比39大,放在右边:38,39,65,97,76
13比39小,放在左边:13,38,39,65,97,76
27比39小,放在左边:27,13,38,39,65,97,76
递归排序子序列:对左右两个子序列分别进行快速排序。由于子序列已经是排好序的(这是最好情况),所以不需要进一步的操作。
结果:27,13,38,39,65,76,97
哈希冲突
已知有一哈希表长度为9,哈希函数为 H(K)=K%9 (取模运算),给定的关键字集合(7,8,30,11,18,9,14,26),请计算集合中关键字的哈希地址,并用拉链法构建哈希表。
我们根据给定的哈希函数 H(K) = K % 9 来计算每个关键字的哈希地址:
H(7) = 7 % 9 = 7
H(8) = 8 % 9 = 8
H(30) = 30 % 9 = 3
H(11) = 11 % 9 = 2
H(18) = 18 % 9 = 0
H(9) = 9 % 9 = 0
H(14) = 14 % 9 = 5
H(26) = 26 % 9 = 8
拉链法
查找成功时的平均查找长度:
每层的个数*第几层/总个数
ASL = (1乘6+2乘4+3乘1+4乘1)/12 = 7/4
查找不成功时的平均查找长度:
总条数长度/哈希表长度
ASL = (4+2+2+1+2+1)/13
线性探測法
查找成功时的查找次数等于插入元素时的比較次数,查找成功的平均查找长度为:
总比较次数/总元素个数
ASL = (1+2+1+4+3+1+1+3+9+1+1+3)/12 = 2.5
查找成功时的查找次数:第n个位置不成功时的比較次数为,第n个位置到第1个没有数据位置的距离:如第0个位置取值为1,第1个位置取值为2.
查找不成功的平均查找长度为:
元素下标和/线性表长度
ASL = (1+2+3+4+5+6+7+8+9+10+11+12)/ 13 = 91/13
线性表
线性表的两种存储结构是什么,各有哪些优点与缺点?
线性表是一种常用的数据结构,它有两种主要的存储结构:顺序存储和链式存储。
顺序存储:
顺序存储结构是将线性表元素以元素的二进制地址为序,以数组形式存储在连续的物理空间中。这种存储方式的优点有:
空间利用率高。因为元素是连续存储的,所以可以利用数组的索引快速访问任何元素。
插入和删除操作方便。只需要移动元素的位置,不需要移动大量的元素。
然而,顺序存储也有一些缺点:
需要预先分配内存空间,可能会导致空间的浪费。如果线性表的实际元素数量小于预分配的空间,那么剩余的空间就不能被利用。
无法动态地扩展线性表的长度。如果需要增加更多的元素,需要重新分配更大的内存空间,并将原数据复制到新的位置,这会增加操作的复杂度和时间成本。
链式存储:
链式存储结构是用一组任意的存储单元存储线性表中的元素。每个元素占用一个存储单元,元素的值与其存储单元的地址无关。链式存储的优点有:
可以动态地扩展线性表的长度。因为链表中的元素不是连续存储的,所以可以在需要时添加新的元素,不需要移动原有的元素。
可以有效地利用内存空间。因为每个元素只占用一个存储单元,所以即使线性表的元素数量很少,也可以有效地利用内存空间。
然而,链式存储也有一些缺点:
访问特定元素的时间复杂度为O(n),因为需要从头节点开始遍历整个链表才能找到目标元素。
需要额外的空间存储节点的指针,增加了内存的使用量。
插入和删除操作可能会涉及到节点的移动和删除,增加了操作的复杂度和时间成本。
现有一个线性表 L= (a,c,a,d,b),求 ListLength(L)、ListEmpty(L)GetElem(L,3,e)、LocateElem(L,a)、ListInsert(L,4,f)和 ListDelete(L,3)等基本运算的执行结果
ListLength(L):这个操作是计算线性表L的长度。给定的线性表L= (a,c,a,d,b),ListLength(L)的结果应该是5。
ListEmpty(L):这个操作是检查线性表L是否为空。因为L包含了5个元素,所以ListEmpty(L)的结果应该是0,即非空。
GetElem(L,3,e):这个操作是获取线性表L中索引为3的元素。给定的线性表L中索引为3的元素是"a",所以GetElem(L,3,e)的结果应该是"a"。
LocateElem(L,a):这个操作是查找线性表L中值为"a"的元素的索引。给定的线性表L中值为"a"的元素索引为1和3,所以LocateElem(L,a)的结果应该是1和3。
ListInsert(L,4,f):这个操作是在线性表L的索引4处插入元素"f"。执行此操作后,新的线性表L应该是 (a,c,a,d,f,b)。
ListDelete(L,3):这个操作是从线性表L中删除索引为3的元素。执行此操作后,新的线性表L应该是 (a,c,a,d,b)。
假定对有序表:(3,4,5,7,24,30,42,54,63,72,87,95) 进行折半查找请回答下列问题:(10 分)
(1)画出描述折半查找过程的判定树;(6 分)
(2) 若查找元素 54,需依次与哪些元素比较? (2分)
(3) 若查找元素 90,需依次与哪些元素比较? (2 分)
顶点(选第几个数):元素总个数+1/元素总个数
ASL:1/元素总个数x总查找次数
查找成功平均查找长度:第几层乘以元素个数之和/总个数
线性表二分查找:第几层乘以元素叶子个数之和/总个数
int binary_search(int arr[], int n, int key) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == key) {
return mid;
} else if (arr[mid] < key) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 没有找到关键字
}
顺序存储二叉树
已知用一维数组存放的一棵完全二叉树:ABCDEFGHIJKL,请画出该二叉树的结构,并写出该树的先序、中序和后序遍历序列。
对于索引为i的节点:
左子节点的索引为2i+1(因为左子节点在父节点的左边的位置)。
右子节点的索引为2i+2(因为右子节点在父节点的右边的位置)
链式存储二叉树
优点
链式存储结构实现二叉树的主要优点包括:
节省空间:对于那些节点度数较小的二叉树,链式存储结构可以节省大量的空间。在顺序存储结构中,我们通常需要为每个节点分配固定的空间,无论该节点是否有子节点。但在链式存储结构中,我们只需要为实际存在的节点分配空间。
插入和删除操作方便:在链式存储结构中,插入和删除一个节点只需要修改相关节点的指针,而不需要移动大量的数据。这使得在二叉树中执行这些操作变得更加容易和高效。
动态性强:链式存储结构可以动态地分配和释放空间,这使得它非常适合用于构建和维护经常变动的二叉树。
扩展性好:对于非完全二叉树,链式存储结构可以很容易地进行扩展,只需要添加新的节点并修改相关的指针即可。
总的来说,链式存储结构实现二叉树的主要优点在于其灵活性、动态性和空间效率。
总结点数
// 求二叉树总结点数
int countNodes(TreeNode *root) {
if (root == NULL) {
return 0;
} else {
return 1 + countNodes(root->left) + countNodes(root->right);
}
}
求深度
// 求二叉树层数
int getTreeDepth(TreeNode *root) {
if (root == NULL) {
return 0;
} else {
int leftDepth = getTreeDepth(root->left);
int rightDepth = getTreeDepth(root->right);
return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;
}
}
求最大值
int findMax(TreeNode *root) {
if (root == NULL) {
return INT_MIN; // 返回INT_MIN表示空树,最大值为最小值
} else {
int max = root->data; // 从根节点开始比较
int leftMax = findMax(root->left); // 在左子树中查找最大值
int rightMax = findMax(root->right); // 在右子树中查找最大值
if (leftMax > max) {
max = leftMax; // 更新最大值
}
if (rightMax > max) {
max = rightMax; // 更新最大值
}
return max; // 返回最大值
}
}
以下是在二叉排序树上查找结点x的C语言算法实现:
TreeNode* searchTreeNode(TreeNode* root, int x) {
// 如果根节点为空,或者根节点的值等于x,直接返回根节点
if (root == NULL || root->val == x) {
return root;
}
// 在左子树中查找值为x的节点
TreeNode* left_result = searchTreeNode(root->left, x);
// 如果在左子树中找到,直接返回结果
if (left_result != NULL) {
return left_result;
}
// 如果在左子树中未找到,则在右子树中查找值为x的节点
return searchTreeNode(root->right, x);
}
二叉树采用链式存储结构,设计算法输出结点值为k 的结点的所有祖先。
void findAncestors(TreeNode *root, int k, TreeNode **ancestors, int *ancestorCount) {
if (root == NULL) {
return;
}
// 将当前节点添加到祖先数组中
ancestors[*ancestorCount] = root;
(*ancestorCount)++;
// 如果当前节点的值等于k,结束递归
if (root->val == k) {
return;
}
// 递归地在左子树和右子树中查找值为k的节点
findAncestors(root->left, k, ancestors, ancestorCount);
findAncestors(root->right, k, ancestors, ancestorCount);
// 回溯,移除当前节点,因为它不是值为k的节点的祖先
(*ancestorCount)--;
}
C语言.对于给定的二叉排序树中的结点 p,设计算法找出其左子树中的最大结点和右子树中的最小结点。
#include <stdio.h>
#include <stdlib.h>
// 定义二叉排序树的节点结构
typedef struct Node {
int data;
struct Node* left;
struct Node* right;
} Node;
// 创建新的节点
Node* newNode(int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->left = NULL;
node->right = NULL;
return node;
}
// 寻找左子树中的最大节点
Node* findMax(Node* node) {
if (node == NULL) {
return NULL;
} else if (node->right != NULL) {
return findMax(node->right); // 递归查找右子树的最大节点
} else {
return node; // 如果右子树为空,那么当前节点就是左子树的最大节点
}
}
// 寻找右子树中的最小节点
Node* findMin(Node* node) {
if (node == NULL) {
return NULL;
} else if (node->left != NULL) {
return findMin(node->left); // 递归查找左子树的最小节点
} else {
return node; // 如果左子树为空,那么当前节点就是右子树的最小节点
}
}
int main() {
Node* root = newNode(10); // 根节点为10
root->left = newNode(7); // 左子节点为7
root->right = newNode(15); // 右子节点为15
root->left->left = newNode(5); // 左子节点的左子节点为5
root->left->right = newNode(9); // 左子节点的右子节点为9
root->right->left = newNode(12); // 右子节点的左子节点为12
root->right->right = newNode(18); // 右子节点的右子节点为18
root->right->left->left = newNode(11); // 12的左子节点为11
root->right->left->right = newNode(14); // 12的右子节点为14
root->right->right->left = newNode(16); // 18的左子节点为16
root->right->right->right = newNode(20); // 18的右子节点为20
// 找到并打印左子树的最大节点和右子树的最小节点
if (root != NULL) {
Node* maxLeft = findMax(root->left);
if (maxLeft != NULL) {
printf("最大左子节点: %d\n", maxLeft->data);
} else {
printf("左子树为空\n");
}
Node* minRight = findMin(root->right);
if (minRight != NULL) {
printf("最小右子节点: %d\n", minRight->data);
} else {
printf("右子树为空\n");
}
} else {
printf("树为空\n");
}
return 0;
}
假设二叉树中每个结点值为单个字符,采用二叉链存储结构存储。设计算法求二叉树中最小值的结点值。
int findMin(TreeNode *root) {
if (root == NULL) {
return INT_MIN;
} else {
char min = root->data;
char leftMin = findMin(root->left);
char rightMin = findMin(root->right);
if (leftMin < min) {
min = leftMin;
}
if (rightMin < min) {
min = rightMin;
}
return min;
}
}
查找给定结点的父亲
// 查找给定节点的父亲
TreeNode* findParent(TreeNode* node, int val) {
if (node == NULL) {
return NULL;
}
if (node->val == val) {
return node->parent;
}
TreeNode* left = findParent(node->left, val);
if (left) {
return left;
}
return findParent(node->right, val);
}
二叉排序树
左子树所有节点值小于根节点值小于右子树所有节点值
中序遍历就是一个有序
已知后序,画图
例子:
一棵以字母为关键字的二叉排序树的后序遍历序列为:ACDBFIJHGE,
完成以下问题
(1)画出该二叉排序树:
(2)计算在等概率下查找成功的平均比较次数:
(3)计算在等概率下查找不成功的平均比较次数
二叉排序树中序就是有序:ABCDEFGHIJ
中后画出
查找成功的平均查找长度为:∑(本层高度*本层元素个数)/节点总数
查找不成功的平均查找长度:∑(本层高度*本层补上的叶子个数)/补上的叶子总数
带头单链表
C语言有一个递增的带头结点的单链表(允许出现重复的值),设计一个算法删除值相同的结点(相同值只保留一个)。 要求:(1) 描述算法的思路; (2)补充完成算法 void Dels (LinkList *&Head)[}
void Dels(LinkList *&head) {
if (head == NULL || head->next == NULL) {
return; // 链表为空或只有一个节点,无需删除
}
LinkList pre = head; // pre指向前一个节点,初始化为头节点
LinkList cur = head->next; // cur指向当前节点,初始化为第二个节点
while (cur != NULL) {
// 如果当前节点与下一个节点的值相同
while (cur->next != NULL && cur->data == cur->next->data) {
cur = cur->next; // 移动到下一个节点
}
// 删除从pre到cur之间的所有节点(不包括pre和cur)
if (pre->next != cur) {
LinkList temp = pre->next;
pre->next = cur;
while (temp != cur) {
LinkList del = temp;
temp = temp->next;
free(del); // 释放内存
}
}
// 移动pre和cur到下一个位置
pre = cur;
if (cur != NULL) {
cur = cur->next;
}
}
}
不带头单链表
设计算法统计出单链表HL 中结点的值等于给定值x的结点数
int countNodesWithValue(Node* head, int x) {
int count = 0; // 初始化计数器为0
Node* current = head; // 初始化当前节点为头节点
// 遍历链表
while (current != NULL) {
if (current->data == x) { // 如果当前节点的值等于给定值x
count++; // 计数器加1
}
current = current->next; // 移动到下一个节点
}
return count; // 返回计数结果,即等于给定值x的节点数
}
请设计一个算法,用于判断单链表中元素是否是递增的
/ 判断链表是否递增
int isIncreasing(struct ListNode* head) {
struct ListNode *p = head;
if (head == NULL) { // 如果链表为空,则认为是递增的
return 1;
}
while (p->next != NULL) { // 遍历链表
if (p->val >= p->next->val) { // 如果当前节点值大于等于下一个节点值,则认为是递减的
return 0;
}
p = p->next; // 移动到下一个节点
}
return 1; // 如果没有出现递减的情况,则认为是递增的
}
循环队列
已知QUEUE 表示循环队列的数据结构,函数 getqueue 取队头元素的值放入变量e,然后删除队头元素,操作成功返回 1,否则返回 0。要求:
(1)简述队列数据操作的特点:
队列是一种先进先出(FIFO,First-In-First-Out)的数据结构,具有两个主要的操作:入队(enqueue)和出队(dequeue)。入队操作在队列的尾部添加元素,而出队操作则从队列的头部移除元素。这两个操作的时间复杂度通常都是O(1),即常数时间内完成,因此队列非常适合用于需要高效添加和移除元素的场景。
特别的,循环队列是一种首尾相连的队列,当尾部的指针到达数组的最后一个位置时,它会回到数组的第一个位置,形成一个环形的结构。这种结构可以有效地利用数组空间,避免在非循环队列中可能出现的空间浪费问题
(2)补充完成算法int getqueue(QUEUE *&q,int *&e)
#include <stdio.h>
#define QUEUE_SIZE 5
//循环队列
typedef struct Queue {
int data[QUEUE_SIZE];
int front; // 队头索引
int rear; // 队尾索引
} Queue;
// 初始化队列
void init(Queue* queue) {
queue->front = 0;
queue->rear = 0;
}
// 判断队列是否为空
int is_empty(Queue* queue) {
return queue->front == queue->rear;
}
// 判断队列是否已满
int is_full(Queue* queue) {
return (queue->rear + 1) % QUEUE_SIZE == queue->front;
}
// 入队操作
void enqueue(Queue* queue, int data) {
if (is_full(queue)) {
printf("队列已满\n");
return;
}
queue->data[queue->rear] = data;
queue->rear = (queue->rear + 1) % QUEUE_SIZE;
}
// 出队操作
int dequeue(Queue* queue) {
if (is_empty(queue)) {
printf("队列为空\n");
return -1; // 返回一个错误码,表示队列为空
}
int data = queue->data[queue->front];
queue->front = (queue->front + 1) % QUEUE_SIZE;
return data;
}
int main (){
Queue q;
init(&q);
enqueue(&q,1);
enqueue(&q,2);
enqueue(&q,3);
enqueue(&q,4);
return 0;
}
假设以顺序存储结构来存储队列,顺序队列的定义如下,请设计出进队列enQueue(q,e)和出队列 deQueue(q,e)的算法(q 表示队列,e 表示队列操作元素
int enQueue(SeqQueue *q, int e) {
if ((q->rear + 1) % MAXSIZE == q->front) {
// 队列已满,无法插入新元素
return 0; // 插入失败,返回0
}
q->data[q->rear] = e; // 将元素e插入队尾
q->rear = (q->rear + 1) % MAXSIZE; // 更新队尾指针
return 1; // 插入成功,返回1
}
int deQueue(SeqQueue *q, int *e) {
if (q->front == q->rear) {
// 队列已空,无法删除元素
return 0; // 删除失败,返回0
}
*e = q->data[q->front]; // 取队头元素赋值给e
q->front = (q->front + 1) % MAXSIZE; // 更新队头指针
return 1; // 删除成功,返回1
}
C语言,将环形队列(容量为 n,元素下标从0到n-1)的元素倒置。
// 倒置环形队列
void reverseQueue(CircularQueue *queue) {
int i, temp;
int n = queue->rear - queue->front + 1; // 队列中元素个数
for (i = 0; i < n / 2; i++) { // 只需要交换前半部分和后半部分的元素即可,因为环形队列是循环的
temp = dequeue(queue); // 出队一个元素到临时变量temp中
enqueue(queue, dequeue(queue)); // 将另一个元素入队,使得它与temp交换位置
enqueue(queue, temp); // 将temp中的元素再入队,完成交换操作
}
}
队列(链表实现)
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int data;
struct node *next;
} Node;
typedef struct queue {
Node *front; // 队头指针
Node *rear; // 队尾指针
} Queue;
// 初始化队列
void init_queue(Queue *q) {
q->front = NULL;
q->rear = NULL;
}
// 判断队列是否为空
int is_queue_empty(Queue *q) {
return q->front == NULL;
}
// 入队操作
void enqueue(Queue *q, int value) {
Node *new_node = (Node*) malloc(sizeof(Node));
new_node->data = value;
new_node->next = NULL;
//空队列首尾指针都指向
if (is_queue_empty(q)) {
q->front = new_node;
q->rear = new_node;
} else {
//原来尾指针指向的下一个是插入节点
q->rear->next = new_node;
//现在尾部指针指向插入节点
q->rear = new_node;
}
}
// 出队操作
int dequeue(Queue *q) {
if (is_queue_empty(q)) {
printf("Queue is empty!\n");
return -1;
}
int value = q->front->data;
Node *temp = q->front;
//首指针现在指向是原来指向节点的下一个节点
q->front = q->front->next;
free(temp);
return value;
}
// 获取队头元素
int get_front(Queue *q) {
if (is_queue_empty(q)) {
printf("Queue is empty!\n");
return -1;
}
return q->front->data;
}
// 获取队列长度
int get_queue_length(Queue *q) {
int length = 0;
Node *current = q->front;
while (current != NULL) {
length++;
current = current->next;
}
return length;
}
// 打印队列元素
void print_queue(Queue *q) {
if (is_queue_empty(q)) {
printf("Queue is empty!\n");
return;
}
printf("Queue elements: ");
Node *current = q->front;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main (){
Queue q;
init_queue(&q);
enqueue(&q,1);
enqueue(&q,2);
enqueue(&q,3);
enqueue(&q,4);
printf("现在队头%d\n", get_front(&q));
printf("现在长度%d\n", get_queue_length(&q));
//出队
dequeue(&q);
printf("现在队头%d\n", get_front(&q));
printf("现在长度%d\n", get_queue_length(&q));
return 0;
}
线性栈
#include <stdio.h>
#include <stdbool.h>
#include <malloc.h>
#define MAX_SIZE 10
typedef int ElemType;
typedef struct {
ElemType data[MAX_SIZE];
int top;
}Stack;
//初始化
void InitStack(Stack *stack){
int length = sizeof(stack->data)/ sizeof(ElemType);
for (int i = 0; i < length; ++i) {
stack->data[i]=0;
}
stack->top=-1;
}
// 判断栈是否为空
int isStackEmpty(Stack *stack) {
return stack->top == -1;
}
// 判断栈是否已满
int isStackFull(Stack *stack) {
return stack->top == MAX_SIZE - 1;
}
// 入栈操作
void push(Stack *stack, int value) {
if (isStackFull(stack)) {
printf("栈已满\n");
return;
}
stack->top++;
stack->data[stack->top] = value;
}
// 出栈操作
int pop(Stack *stack) {
if (isStackEmpty(stack)) {
printf("栈已空\n");
return -1;
}
int value = stack->data[stack->top];
stack->top--;
return value;
}
// 获取栈顶元素
int getTop(Stack *stack) {
if (isStackEmpty(stack)) {
printf("栈已空\n");
return -1;
}
return stack->data[stack->top];
}
int main() {
//声明
Stack s;
//初始化
InitStack(&s);
//入栈
push(&s,1);
push(&s,2);
push(&s,3);
push(&s,4);
//获取栈顶
printf("栈顶元素:%d\n", getTop(&s));
//出栈
printf("%d\n", pop(&s));
printf("%d\n", pop(&s));
printf("%d\n", pop(&s));
printf("%d\n", pop(&s));
return 0;
}
链式栈
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点
typedef struct Node {
int data;
struct Node* next;
} Node;
// 初始化栈
Node* initializeStack() {
return NULL;
}
// 判断栈是否为空
int isEmpty(Node* top) {
return top == NULL;
}
// 向栈中压入元素
void push(Node** top, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = *top;
*top = newNode;
}
// 从栈中弹出元素
int pop(Node** top) {
if (isEmpty(*top)) {
printf("Stack underflow. Program terminated\n");
exit(EXIT_FAILURE);
} else {
Node* temp = *top;
int popped = temp->data;
*top = temp->next;
free(temp);
return popped;
}
}
// 获取栈顶元素
int peek(Node* top) {
if (!isEmpty(top)) {
return top->data;
} else {
printf("Stack underflow. Program terminated\n");
exit(EXIT_FAILURE);
}
}
int main() {
Node* top = initializeStack();
push(&top, 10);
push(&top, 20);
push(&top, 30);
printf("Top element is: %d\n", peek(top));
printf("Popped element: %d\n", pop(&top));
printf("Top element is: %d\n", peek(top));
return 0;
}
假设表达式中允许包含 3 种括号: 圆括号、方括号和大括号。设计算法判断给定表达式中的括号是否正确配对。
为了判断给定表达式中的括号是否正确配对,我们可以使用一个栈。遍历表达式中的每个字符,如果字符是左括号,则将其压入栈中。如果字符是右括号,则检查栈是否为空,或者栈顶的左括号是否与当前的右括号配对。如果栈为空或栈顶的左括号与当前的右括号不匹配,则表达式中的括号没有正确配对。
以下是一个简单的 C 语言算法,用于判断给定表达式中的括号是否正确配对:
c
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
char data[MAX_SIZE];
int top;
} Stack;
void push(Stack *s, char c) {
if (s->top == MAX_SIZE - 1) {
printf("Error: stack overflow\n");
exit(1);
}
s->data[++s->top] = c;
}
char pop(Stack *s) {
if (s->top == -1) {
printf("Error: stack underflow\n");
exit(1);
}
return s->data[s->top--];
}
int is_valid(char *expr) {
Stack s;
s.top = -1;
int i = 0;
while (expr[i] != '\0') {
if (expr[i] == '(' || expr[i] == '[' || expr[i] == '{') {
push(&s, expr[i]);
} else if (expr[i] == ')' || expr[i] == ']' || expr[i] == '}') {
if (s.top == -1 || (expr[i] == ')' && pop(&s) != '(') || (expr[i] == ']' && pop(&s) != '[') || (expr[i] == '}' && pop(&s) != '{')) {
return 0;
}
} else {
continue;
}
i++;
}
return s.top == -1;
}
int main() {
char expr[MAX_SIZE];
printf("Enter an expression: ");
scanf("%s", expr);
if (is_valid(expr)) {
printf("The brackets in the expression are valid.\n");
} else {
printf("The brackets in the expression are not valid.\n");
}
return 0;
}
该算法使用一个栈来存储左括号,并在遇到右括号时检查栈是否为空或栈顶的左括号是否与当前的右括号配对。如果表达式中的括号没有正确配对,则返回 0,否则返回 1。
查找
二分查找
#include <stdio.h>
int binarySearch(int arr[], int left, int right, int target) {
//首位值和尾位置
while (left <= right) {
//中间位置
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
//如果目标小于中间,尾部位置是中间位置减一
} else if (arr[mid] > target) {
right = mid - 1;
//如果目标小于中间,尾部位置是中间位置减一
} else {
left = mid + 1;
}
}
return -1; //目标元素不存在
}
int main() {
int arr[] = {1, 3, 5, 7, 9};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 5;
int index = binarySearch(arr, 0, n - 1, target);
if (index == -1) {
printf("目标元素不存在\n");
} else {
printf("目标元素的下标为:%d\n", index);
}
return 0;
}
排序算法
请详细给出{5,10,20,2,8,7,11,9,14)的冒泡排序过程(升序)
第一轮:
比较第1个和第2个数,如果第1个数比第2个数大,就交换它们的位置,否则保持不变。
比较第2个和第3个数,如果第2个数比第3个数大,就交换它们的位置,否则保持不变。
以此类推,比较第n-1个和第n个数。这一轮结束后,最大的数被放到了最后。
第二轮:
比较第1个和第2个数,如果第1个数比第2个数大,就交换它们的位置,否则保持不变。
比较第2个和第3个数,如果第2个数比第3个数大,就交换它们的位置,否则保持不变。
以此类推,比较第n-2个和第n-1个数。这一轮结束后,次大的数被放到了倒数第二的位置。
重复上述过程,直到所有的数都被排序。
对于给定的序列(5,10,20,2,8,7,11,9,14),第一轮的结果是(2,5,8,7,10,11,9,14,20),第二轮的结果是(2,5,7,8,10,9,11,14,20),第三轮的结果是(2,5,7,8,9,10,11,14,20),第四轮的结果是(2,5,7,8,9,10,11,14,20),因为序列已经有序了
现有关键字序列{265,301,751,129,937,863,742,694,76,438),请给出冒泡排序算法各趟排序后关键字序列的状态。
用C 语言写一算法,将顺序表L 中的所有元素逆置,要求算法空间复杂度为 0(1)
#include <stdio.h>
void reverse(int L[], int n) {
int temp, i, j;
for (i = 0, j = n - 1; i < j; i++, j--) {
temp = L[i];
L[i] = L[j];
L[j] = temp;
}
}
int main() {
int L[] = {1, 2, 3, 4, 5};
int n = sizeof(L) / sizeof(L[0]);
int i;
printf("Original array: ");
for (i = 0; i < n; i++) {
printf("%d ", L[i]);
}
printf("\n");
reverse(L, n);
printf("Reversed array: ");
for (i = 0; i < n; i++) {
printf("%d ", L[i]);
}
printf("\n");
return 0;
}
AVL树
设有一组初始记录关键字为(45,80,48,40,22,78),请构造一棵AVL 树并给出构造过程
创建根节点,将第一个关键字45作为根节点的值。这是一个空的AVL树。
插入第二个关键字80。由于80大于45,我们将它在45的右子树中插入。此时,树的左子树为空,右子树的值为80。
插入48。48小于45和80,因此它成为45的左子节点。此时,树的左子树为45->48,右子树为80。
插入40。40小于45和48,因此它成为45的左子节点。此时,树的左子树为45->48->40,右子树为80。
插入22。22小于45和48,因此它成为45的左子节点。此时,树的左子树为45->48->40->22,右子树为80。
插入78。78大于45、48、40和22,因此它成为80的右子节点。此时,树的左子树为45->48->40->22,右子树为80->78。
已知有一组关键字为(16,3,7,11,9)的初始记录,请详细给出构造一棵 AVL 树的详细过程。
插入16:
16作为根节点。
高度为1(因为只有一个节点)。
插入3:
3小于16,所以插入到16的左子树。
更新16的高度为2(因为它有一个子节点)。
插入7:
7小于16但大于3,所以插入到3的右子树。
更新3的平衡因子为1(右子树高度减去左子树高度)。
更新16的高度为3。
插入11:
11小于16但大于9(此时9还未插入,但我们可以预见9会插入在7和11之间),所以插入到7的右子树。
更新7的平衡因子为1。
更新3的平衡因子为2(此时,3的右子树比左子树高2,违反AVL树的性质)。
进行旋转操作来恢复平衡。这里执行的是左旋转操作,围绕节点3进行旋转。旋转后,7成为新的根,3成为7的左子树,而原本7的左子树(无节点)成为3的右子树。同时,更新相关节点的高度和平衡因子。
插入9:
9小于16且大于7,所以插入到7的右子树。
更新7的平衡因子为0(左子树和右子树的高度相同)。
更新其他相关节点的高度和平衡因子。
LL: 右旋
RR:左旋
LR:先左旋调整LL,在右旋
RL:先右旋调整为RR,在左旋
X旋,成为X孩子
哈夫曼编码
广义表
head[(x,y,z)]求广义表的运算结果
在广义表中,`head`表示表头,而`tail`表示表尾。对于一个广义表 `(a1, a2, ..., an)`,`head`操作返回第一个元素 `a1`,而`tail`操作返回除第一个元素外的剩余部分 `(a2, ..., an)`。
对于你给出的 `head[(x,y,z)]`,这是一个不完整的表达式。通常,我们需要知道这个操作是在哪个广义表上进行的。如果你想对广义表 `(x,y,z)` 进行 `head` 操作,那么结果应该是 `x`,因为 `(x,y,z)` 的第一个元素是 `x`。
因此,对于广义表 `(x,y,z)`,`head` 操作的结果是 `x`。
tail[((a,b),(x,y))]求广义表的运算结果
我们要对一个广义表进行tail操作。
广义表是一个嵌套的列表,它可以包含其他列表作为其元素。
tail操作是获取广义表的除第一个元素外的所有元素。
假设我们的广义表为G,其结构为:
G = ((a,b),(x,y))
对于广义表G,tail操作会返回除第一个元素外的所有元素。
所以,tail[G] = (x,y)
计算结果为:tail[G] = ('x', 'y')
所以,tail[((a,b),(x,y))]的运算结果是:('x', 'y')。
选择排序-O(n^2)
第一个与后面比较,大;交换
#include <stdio.h>
int main() {
int array [8]={4,1,6,8,2,3,9,5};
int l = sizeof (array)/sizeof (int);
printf("选择排序前:");
for(int i=0;i<l;i++){
printf("%d ",array[i]);
};
for(int i = 0;i<l-1;i++){
for(int j = i+1;j<l-i;j++){
if(array[i]>array[j]){
int temp =array[i];
array[i]=array[j];
array[j]=temp;
}
}
};
printf("选择排序后:");
for(int i=0;i<l;i++){
printf("%d ",array[i]);
};
return 0;
}
冒泡排序-O(n^2)
两两比较–比它大交换–第一次循环找出最大
#include <stdio.h>
int main() {
int array [8]={4,1,6,8,2,3,9,5};
int l = sizeof (array)/sizeof (int);
printf("冒泡排序前:");
for(int i=0;i<l;i++){
printf("%d ",array[i]);
};
for(int i = 0;i<l;i++){
for(int j = 0;j<l-i-1;j++){
if(array[j]>array[j+1]){
int temp =array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
};
printf("冒泡排序后:");
for(int i=0;i<l;i++){
printf("%d ",array[i]);
};
return 0;
}
直接插入排序-O(n^2)
元素移动位置
#include <stdio.h>
int main() {
int array [8]={1,5,6,4,3,2,9,8};
int l = sizeof (array)/sizeof (int);
printf("插入排序前:");
for(int i=0;i<l;i++){
printf("%d ",array[i]);
};
for(int i = 1;i<l;i++){
//从第一个与前面已经排好序,从第一个开始
int j = i-1;
int temp = array[i];
while(j>=0&&array[j]>temp){
//循环判断比i位置大的都后移(已经排好序可以直接后移动一位)
array[j+1]=array[j];
j--;
};
//循环结束后的当前j的后一位是i位置的值
array[j+1]=temp;
};
printf("插入排序后:");
for(int i=0;i<l;i++){
printf("%d ",array[i]);
};
return 0;
}
指针
在数组中,*p++
和 *(p++)
是等价的。
先拿到*p的值,p在指向下一个
++有赋值操作。+是单纯的运算
#include <stdio.h>
#include <intrin.h>
int main(){
int arr[10] = {1,4,7,10,13,16,19,22,25};
//c语言数组名是第一个元素的地址
int *p = arr;
int *p1 = arr+1;//数组第二个元素的地址
//int *p2 = arr++;//不允许,编译报错
printf("%d ",*p);
printf("%d ",*p++); //先打印*p ;p在指向下一个
printf("%d ",*p);
printf("%d ",*(p++)); //先打印*p ;p在指向下一个
printf("%d ",*p);
printf("%d ",*p+1);//先拿出*p,在打印*p加1的值,本身没有发生变化
printf("%d ",*p);
printf("%d ",*(p+1));//指向下一个地址,在拿出来
printf("%d ",*p);
return 0;
}
#include <stdio.h>
#include <intrin.h>
int main(){
int arr[10] = {1,4,7,10,13,16,19,22,25};
//c语言数组名是第一个元素的地址
int *p = arr+1;
int *p1 = arr+1;//数组第二个元素的地址
int *p2 = arr++;//不允许,编译报错
printf("%d ",(*p)++); //先拿出*p,对应值+=1
printf("%d ",*p);
printf("%d ",*--p);//指针指向上一个,在打印
printf("%d ",*p);
printf("%d ",--*p);//先拿出*p,对应数据在-=
printf("%d ",*p);
printf("%d ",*p--);//打印*p p往前
printf("%d ",*p);
printf("%d ",(*p)--);//打印*p,值—=
printf("%d ",*p);
return 0;
}
指针函数
看后面,就是一个函数
返回函数结果的地址
定义:int *f()
#include <stdio.h>
int *f(){
static int x = 10; //全局使用
return &x; //返回x的地址
}
int main() {
int *r = f(); //接受返回地址
printf("%d\n",*r); //打印地址对应的值
return 0;
}
函数指针
看后面就是一个指针 ;一个指向函数的指针;在内存空间中存放的是函数的地址;
int add(int a,int b){
return a+b;
}
int main() {
int (*r)(int a,int b) = &add; //函数指针接受函数地址
int out = r(2,3); //调用函数
printf("%d\n",out);
return 0;
}
时间复杂度
对于带头节点的循环双链表,以下是各种操作的时间复杂度:
查找序号为i(1≤i≤n)的元素:O(i)
查找第一个值为x的元素:O(n)
插入新元素作为第一个元素:O(1)
插入新元素作为最后一个元素:O(1)
插入第i(2≤i≤n)个元素:O(i)
删除第一个元素:O(1)
删除最后一个元素:O(1)
删除第 i(2≤i≤n)个元素:O(i)
图
有向图:
邻接矩阵转无向图
邻接矩阵
做辅助判断有几个顶点
构建无向图
深度优先搜索随便写,个人习惯选择权重最小