中国大学MOOC-陈越、何钦铭-数据结构-2019夏

----------------------函数题 (共 65 分) ----------------------------

01-复杂度1 最大子列和问题

算法1:在线算法。

#include <cstdio>
int main()
{
    int K, MaxSum = 0, ThisSum = 0, number;
    scanf("%d", &K);
    for (int i = 0; i < K; i++) {
    	scanf("%d", &number);
        ThisSum += number;
        if (ThisSum > MaxSum) MaxSum = ThisSum;
        if (ThisSum < 0) ThisSum = 0;
    }
    printf("%d", MaxSum);
}

算法2:分治法,O(nlogn)

#include <cstdio>
int a[100100];
int Max3( int A, int B, int C )
{   /* 返回3个整数中的最大值 */
    return A > B ? A > C ? A : C : B > C ? B : C;
}
int MaxSubSum(int A[], int left, int right) 
{   /* 分治法求List[left]到List[right]的最大子列和 */
    int MaxLeftSum, MaxRightSum, mid = (left + right) / 2; /* 存放左右子问题的解 */
    /* Base Case 将负数处理为0*/ 
    if (left == right) {  /* 递归的终止条件,子列只有1个数字 */
        return  A[left] > 0 ? A[left] : 0;
    }
    /* DIVIDE PART */
    /* Recursive Case 递归地分成两个子问题求解, 求得两边子列的最大和 */  
    MaxLeftSum = MaxSubSum(A, left, mid);
    MaxRightSum = MaxSubSum(A, mid + 1, right);

    /* CONQUEUR PART 下面求跨分界线的最大子列和 */  
    int MaxLeftBorderSum, LeftBorderSum, MaxRightBorderSum, RightBorderSum; /*存放跨分界线的结果*/
    /* 求出前半部分的最大和 */
    MaxLeftBorderSum = LeftBorderSum = 0;
    for (int i = mid; i >= left; i--) {    /* 从中线向左扫描 */
        LeftBorderSum += a[i];
        if (LeftBorderSum > MaxLeftBorderSum) MaxLeftBorderSum = LeftBorderSum;
    } /* 左边扫描结束 */
    /* 求出后半部分的最大和 */
    MaxRightBorderSum = RightBorderSum = 0; 
    for (int i = mid+1; i <= right; i++) { /* 从中线向右扫描 */
        RightBorderSum += a[i];
        if (RightBorderSum > MaxRightBorderSum) MaxRightBorderSum = RightBorderSum;    
    } /* 右边扫描结束 */
    /* 下面返回"治"的结果 */
    return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum);
}
int MaxSubsequence(int A[], int k) { /* 保持与前2种算法相同的函数接口 */
    return MaxSubSum(A, 0, k-1);
}
int main()
{
    int k;
    scanf("%d", &k);
    for (int i = 0; i < k; i++) {
        scanf("%d", &a[i]);
    }
    printf("%d", MaxSubsequence(a, k));
}

02-线性结构1 两个有序链表序列的合并

这里的关键在于对于带头结点的链表的操作,其为空表,需要将头结点的指针域置为NULL。
这里不知道为什么不可以比较p指针和l1指针,判断它们是否都指向链表第一个数据结点,所以只好使用flag。

List Merge( List L1, List L2 ) {
    /*L1 L2均指向无实际数据的头结点 */ //List l1 = L1->Next, l2 = L2->Next;
    if (!L1->Next || !L2->Next) return L1 ? L1 : L2;
    List Head = (List)malloc(sizeof(struct Node)), This, p = L1->Next, q = L2->Next;
    L1->Next = L2->Next = NULL; /* 使其为空,需要将头结点的指针域置为NULL */
    int flag = 0;
    while (p && q) {
        if (p->Data <= q->Data) {
            if (!flag) { /* 不可以比较p和l1 = L1->Next, 回避指针的直接比较*/
            	Head->Next = p;
            	flag = 1;
			}
            else This->Next = p;
            This = p;
            p = p->Next;
            if (!p) This->Next = q;
            
        } else {
            if (!flag) {
            	Head->Next = q;
            	flag = 1;
			}
            else This->Next = q;
            This = q;
            q = q->Next;
            if (!q) This->Next = p;
        }
    }
    return Head;
}

04-树7 二叉搜索树的操作集 (30 分)

BinTree Insert(BinTree BST, ElementType X) {
	/* Find操作的改版 在找不到的时候进行操作; 函数Insert将X插入二叉搜索树BST并返回结果树的根结点指针 */
	if (BST == NULL) {
        BST = (BinTree)malloc(sizeof(struct TNode));
		BST->Data = X;
		BST->Left = BST->Right = NULL;
	} else {
		if (X < BST->Data) BST->Left = Insert(BST->Left, X);
		else if (X > BST->Data) BST->Right = Insert(BST->Right, X);
		/* else do nothing */
	}
	return BST; /* Don't forget this line! */
}
BinTree Delete(BinTree BST, ElementType X) {
	/* Find操作的改版 在找到后进行判断和操作; 
	函数Delete将X从二叉搜索树BST中删除,并返回结果树的根结点指针;如果X不在树中,
	则打印一行Not Found并返回原树的根结点指针 */
	if (BST == NULL) {
		printf("Not Found\n");
	} else {
		if (X < BST->Data) BST->Left = Delete(BST->Left, X);
		else if (X > BST->Data) BST->Right = Delete(BST->Right, X);
		else { 
		    Position TmpCell;
            if (BST->Left && BST->Right) { /*BST has both two child */
            	TmpCell = FindMin(BST->Right);
            	BST->Data = TmpCell->Data; /* 找到右子树的最小值放在根上面, 它大于左子树 */
            	/* 递归删除右子树最小元, 它没有左子树 */ 
            	BST->Right = Delete(BST->Right, TmpCell->Data); 
			} else { /* zero childTree or one child, left or right */
            	TmpCell = BST; /* Don't forget! */
            	if (BST->Left) {         /* 左子树非空 */
            		BST = BST->Left; 
				} else if (BST->Right) { /* 右子树非空 */
					BST = BST->Right;
				} else {                 /* 为叶子结点 */
					BST = NULL;
				} 
            	free(TmpCell);
			}
		}
	}
	return BST;
}
Position Find(BinTree BST, ElementType X) {  /* 递归版本 */
	/* 函数Find在二叉搜索树BST中找到X,返回该结点的指针;如果找不到则返回空指针 */
	if (BST == NULL) return NULL;
	else {
		if (X < BST->Data) return Find(BST->Left, X);
		else if (X > BST->Data) return Find(BST->Right, X);
		else return BST;
	}
}
Position FindMin(BinTree BST) {  /* 递归版本 */ 
    /* 函数FindMin返回二叉搜索树BST中最小元结点的指针 */
	if (BST == NULL) return NULL;
	else {
		if (BST->Left == NULL) return BST;
		else return FindMin(BST->Left);
	}
} 
Position FindMax(BinTree BST) {  /* 递归版本 */
    /* 函数FindMax返回二叉搜索树BST中最大元结点的指针 */
	if (BST == NULL) return NULL;
	else {
		if (BST->Right == NULL) return BST;
		else return FindMax(BST->Right);
	}
} 

----------------------编程题 (共 845 分)---------------------------

01-复杂度2 Maximum Subsequence Sum

这道题是最大子列问题的变体,不同的是要求输出最大子列的第一位数和最后一位数,而且这道题还多加了一些限制

  • If all the K numbers are negative, then its maximum sum is defined to be 0, and you are supposed to output the first and the last numbers of the whole sequence.数组全是负数时,要按“0 数组第一位数 数组最后一位数”输出。 如-5 -2 -5要按0 -5 -5输出;
  • The maximum subsequence is not unique, output the one with the smallest indices i and j. 比如:并列和对应相同i但是不同j,即尾是0,输出(MaxSum, i, min(j1, j2,...);最大和前面有一段是0,输出(MaxSum, 0, j)
  • 数组不全为负数时,又分为:
    • 没有正数,即负数和0,如-5 0 -6要按0 0 0输出
    • 有正数,有正数时要考虑一些特殊情况。如:-11 0 2 5这是输出的最大子列和的第一位数和最后一位数是0 5.
    • ③当结果不唯一时,要求输出结果对应的具有最小的i和j。即当两个并列和对应相同i但是不同j,即尾是0,这时考虑的哪个元素个数少就取哪个。

可以说看懂了这两句话才看懂了这个题目。

#include <cstdio>
int a[10010];
int main()
{
    int K;
    scanf("%d", &K);
    int ThisSum, ThisLeft = 0;  // 指示当前子列和的左位序, 如果当前子列和不小于0不会变化  
    int MaxSum = -1, MaxLeft = 0, MaxRight = K-1; // 指示最大子列和的左右位序,需要更新的时候就分别更新
    for (int i = 0; i < K; i++) {
        scanf("%d", &a[i]);    
        ThisSum += a[i];
        if (ThisSum > MaxSum) {
            MaxSum = ThisSum;
            MaxLeft = ThisLeft; 
            MaxRight = i;     
        } else if (ThisSum < 0) { // 这里一定要用else if??
            ThisSum = 0;
            ThisLeft = i + 1;
        }
    }
    if (MaxSum < 0) 
        printf("0 %d %d", a[MaxLeft], a[MaxRight]);  
    else 
        printf("%d %d %d", MaxSum, a[MaxLeft], a[MaxRight]);        
    return 0;
}

My Problem
这题我有很大的疑惑,为什么写else if就全部正确,而写else就全部错误呢?在我的电脑里面两者测试了好几组数据都是相同的结果。
在这里插入图片描述在这里插入图片描述

02-线性结构2 一元多项式的乘法与加法运算 (20 分)

#include <cstdio>
#include <cstdlib>
/* 一元多项式结构表示 */
typedef struct pNode *PtrToNode;
struct pNode {
	int coef;
	int expo;
	PtrToNode next;	
}; 
typedef PtrToNode Polynomial;
int IsEmpty(Polynomial p) {
	return p->next == NULL;
}
Polynomial AttachOne(int coef, int expo, Polynomial *pRear) {
	Polynomial p = (Polynomial)malloc(sizeof(struct pNode));
	p->coef = coef;
	p->expo = expo;
	p->next = NULL;
	/* 将p指向的新结点插入到结果表达式尾项的后面 */
	(*pRear)->next = p;
	*pRear = p;  /*修改pRear值 */ 
}
/* 创建带头链表 */ 
Polynomial CreatePoly() {
    Polynomial head = (Polynomial)malloc(sizeof(pNode));
	if (head == NULL) printf("Fatal Error!");
	else head->next = NULL;
	return head;	
}
/* 读入数据创建并初始化一元多项式 */ 
Polynomial InitPolynomial() {
	int N, coef, expo;
	scanf("%d", &N);
	Polynomial head = CreatePoly(), rear = head;  /* head为空的表头结点 */
	while (N--) {
		scanf("%d %d", &coef, &expo);	  // 以系数指数对形式输入 
		AttachOne(coef, expo, &rear); 
	}
	return head;
}

/* 将未处理完的另一个多项式的所有节点依次复制到结果多项式中去 */
void AttachAll(Polynomial head, Polynomial other) {
	while (other) {
		Polynomial item = (Polynomial)malloc(sizeof(pNode));
		item->coef = other->coef;
		item->expo = other->expo;
		item->next = NULL;
		/* 移动到下一个结点 */
		head->next = item;
		head = item;
		other = other->next;
	}
}
int Compare(int n, int m) {
	if (n > m) return 1;
	else if (n == m) return 0;
	else return -1;
}
Polynomial AddPoly(Polynomial head1, Polynomial head2) {
	/*算法思路: 两个指针P1和P2分别指向这两个多项式第一个结点,不断循环:
    1. P->expo > Q->expo: 将P的当前项存入结果多项式,并使P指向下一项;
	2. P->expo < Q->expo: 将Q的当前项存入结果多项式,并使Q指向下一项;
	3. P->expo == Q->expo: 系数相加,若结果不为0,则作为结果多项式对应项
	的系数。同时,P和Q都分别指向下一项;
	4. 当某一多项式处理完时,将另一个多项式的所有结点依次复制到结果多项式中去.
	*/
	Polynomial p = head1->next, q = head2->next, front = CreatePoly();
	Polynomial r = front;
	while (p && q) {   /* 当两个多项式都有非零项待处理时 */
		switch(Compare(p->expo, q->expo)) {
			case 1:
				AttachOne(p->coef, p->expo, &r);
				p = p->next;
				break;
			case -1:
				AttachOne(q->coef, q->expo, &r);
				q = q->next;
				break; 
			case 0:   
 		        int k = p->coef + q->coef;
				if (k) AttachOne(k, p->expo, &r);  /* 零多项式自然为空 */	
	            p = p->next;
				q = q->next;
				break;
		}
	}
	if (p) AttachAll(r, p); 
	else if (q) AttachAll(r, q);
	/* 上面两行可以这么写,可以少一个函数 
	for (; p; p = p->next) Attach(p->coef, p->expo, &rear);
	for (; q; q = q->next) Attach(q->coef, q->expo, &rear);
	rear->next = NULL;*/ 
	return front;
}

Polynomial MultiPoly(Polynomial head1, Polynomial head2) {
	if (IsEmpty(head1)) return head1; /* 多项式不存在即为零多项式, 结果为0 */ 
	else if (IsEmpty(head2)) return head2;
	
	Polynomial p = head1->next, q = head2->next;
	/* 实现方法 
	1.将乘法运算转换为加法运算, 将P当前项(ci,ei)乘P2多项式, 再加到结果多项式里--太麻烦了 
    2. 逐项插入: 将P当前项(c1i ,e1i)乘Q当前项(c2i,e2i), 并插入到结果多项式中.
	关键是要找到插入位置. 初始结果多项式可由P第一项乘Q获得 */
	int c, e;
	Polynomial front = CreatePoly(), r = front, temp;
	while (q) { /* 初始结果多项式可由P第一项乘Q获得 */
		AttachOne(p->coef * q->coef, p->expo + q->expo, &r);
		q = q->next; 
	}
	p = p->next; /* p第一项结束 */
	while (p) {
		q = head2->next; /* q重回第一项 */
		r = front;       /* 结果多项式的r指向表头结点 */ 
		while (q) {
			c = p->coef * q->coef;
			e = p->expo + q->expo;
			while (r->next && r->next->expo > e)   
	            r = r->next;
			if (r->next && r->next->expo == e) {
			    if (r->next->coef + c) {
			    	r->next->coef += c;
				} else {
					temp = r->next;
					r->next = temp->next; // 0项排除 
					free(temp);
				}		 
			}	else {
				temp = (Polynomial)malloc(sizeof(struct pNode));
				temp->coef = c;
				temp->expo = e;
				temp->next = r->next; /* 必须! */
				r->next = temp;
				r = r->next;
			}
			q = q->next;
		}
		p = p->next;
	}
	return front;
}

void PrintPoly(Polynomial head) {
	Polynomial p = head;
	if (IsEmpty(p)) {
	   printf("0 0\n");
	   return;
    }
	p = p->next;
	while (p->next) {
	    printf("%d %d ", p->coef, p->expo);
		p = p->next;	
	}
	printf("%d %d\n", p->coef, p->expo);
} 

int main()
{
	Polynomial p1 = InitPolynomial();
	Polynomial p2 = InitPolynomial();
    
    Polynomial new1 = MultiPoly(p1, p2);
    Polynomial new2 = AddPoly(p1, p2);
    PrintPoly(new1);
    PrintPoly(new2);
    
	return 0;
}

02-线性结构3 Reversing Linked List (25 分)

应该考虑输入样例中有不在链表中的结点的情况。所以用个sum计数。而且,algorithm头文件里面有reverse函数可以直接调用

最大N,最后剩K-1不反转	答案正确
有多余结点不在链表上	答案正确
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
//静态链表
struct node {
    int data, next;
} List[maxn];

int main() {
    int first, n, k, temp;
    scanf("%d%d%d", &first, &n, &k);
    while (n--) {
        cin >> temp;
        cin >> List[temp].data >> List[temp].next;
    }
    int sum = 0, link[maxn]; //记录存在链表中的元素个数
    while (first != -1) {
        link[sum++] = first;
        first = List[first].next; //记录头指针和链接
    }
    for (int i = 0; i < sum - sum % k; i += k)  //剩下的小于k的不反转
        reverse(link + i, link + i + k);
    for (int i = 0; i < sum - 1; i++)  
        printf("%05d %d %05d\n", link[i], List[link[i]].data, link[i + 1]);
    printf("%05d %d -1", link[sum - 1], List[link[sum - 1]]); //打印最后一个结点
    return 0;
}

02-线性结构4 Pop Sequence (25 分)

如何判断一个容量为N的堆栈能否形成依次进入的M个数的一个合法出栈序列?

一个相关的问题是:N个数以一定的顺序依次入栈(栈容量够大),(合法的)出栈顺序有多少种?

  • 比如,AB两个元素,入栈顺序为AB,出栈情况有两种:
    (1)入A,出A,入B,出B,出栈顺序为AB;
    (2)入A,入B,出B,出A,出栈顺序为BA。
    因此,2个元素时,结果为2。
    显然f(1)=1,f(2)=2
  • 我们现在来分析一般情况。一般情况下,我们可以按照“第一个入栈的元素,在出栈序列中的位置”作为分类手段。
    举个例子,我们假设入栈元素为A,B,C,D。我们按照“A在出栈序列中的位置”分类讨论:
    (1)当A第一个出栈时,A先进,然后马上出栈。这种情况下,共有“BCD出栈顺序的种类数”种方案。也就是f(n-1)。
    (2)当A第二个出栈时,A先进,B再进,之后B需要马上出来(这样才能确保A排第二)。此时共有f(n-2)种方案。
    (3)当A第三个出栈时,A先进,之后只要确保排在A后面两个的元素比A先出即可。此时共有f(2)*f(1)种方案。f(2)是指“BC入栈出栈顺序的种类数”,f(1)是指”D入栈出栈的种类数”。
    ……
    分析到这里,规律就很显然了。
    在这里插入图片描述
    从第一项开始,分别是第一个入栈元素在第i+1个出栈的情况数。上式中,令f(0)=1 。
    这个实际上是卡特兰数(Catalan number,又称卡塔兰数)。若编程实现,需要维护一个一维数组,时间复杂度为O(n^2)。(递归实现的时间复杂度太高)。卡塔兰数的通项公式为h(n)=C(2n,n)-C(2n,n+1)(n=0,1,2,…)。
    直接公式:
    令h(0)=1,h(1)=1,卡特兰数满足递推式:h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)
    例如:
    h(2)=h(0)h(1)+h(1)h(0)=11+11=2,
    h(3)=h(0)h(2)+h(1)h(1)+h(2)h(0)=12+11+21=5
    递推关系的解为:h(n)=C(2n,n)/(n+1) (n=1,2,3,…)
    递推关系的另类解为:h(n)=C(2n,n)-C(2n,n+1)(n=1,2,3,…)

常规分析:

  1. 首先,我们设f(n)代表序列个数为n的出栈序列种数。同时,我们假定第一个出栈的序数是k。
  2. 第一个出栈的序数k将1-n的序列分成两个序列:其中一个是1-k-1,序列个数为k-1;另外一个是k+1~n,序列个数是n-k。
  3. 此时,我们若把k视为一个确定的序数,那么根据乘法原理,f(n)的问题就等价于序列个数为k-1的出栈序列种数乘以序列个数为n - k的出栈序列种数,即选择k这个序数的出栈组合为f(k-1)×f(n-k)。
  4. 而k可以选1到n,所以再根据加法原理,将k取不同值的序列种数相加,得到的总序列种数为:
    f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0)。
  5. 看到此处,再看看卡特兰数的递推式,答案不言而喻,即为: f(n)=h(n)=C(2n,n)/(n+1)=C(2n,n)-C(2n,n+1), (n=1,2,3,……)。最后,令f(0)=1,f(1)=1。
#include <cstdio>
#include <cstdlib>
#define EmptyTOS (-1)
#define Error (0)
 
typedef int ElementType;
typedef struct StackRecord {
	int Capacity;
	int TopOfStack;
	ElementType *Array; 
} StackRecord; 
typedef struct StackRecord *Stack;

Stack CreateStack(int MaxSize);
void MakeEmpty(Stack S);
int IsEmpty(Stack S);
int IsFull(Stack S);
void Push(ElementType X, Stack S);
ElementType Pop(Stack S);
void DisposeStack(Stack S);

Stack CreateStack(int MaxSize) {
	Stack S = (Stack)malloc(sizeof(StackRecord));
	S->Array = (ElementType *)malloc(sizeof(ElementType) * MaxSize);
	S->Capacity = MaxSize;
	MakeEmpty(S); /* 将栈设为空栈 */ 
	return S;
}
void MakeEmpty(Stack S) {
	S->TopOfStack = EmptyTOS; 
}
int IsEmpty(Stack S) {
	return S->TopOfStack == EmptyTOS;
}
int IsFull(Stack S) {
	return S->TopOfStack == (S->Capacity - 1); 
}
void Push(ElementType X, Stack S) {
	if (IsFull(S)) {
        printf("Full Stack");
		return;
	} else 
        S->Array[++S->TopOfStack] = X;
}
ElementType Top(Stack S) { /* 返回栈顶但不使得栈改变 */
	if (IsEmpty(S)) {
		printf("Empty Stack");
		return 0;
	} else 
        return S->Array[S->TopOfStack]; 
}
ElementType Pop(Stack S) { /* 返回栈顶但使得栈改变 */ 
	if (IsEmpty(S)) {
		printf("Empty Stack");
		return Error;      /* Error是ElementType的特殊值, 标志错误 */
	} else 
        return S->Array[S->TopOfStack--];
}
void DisposeStack(Stack S) {
	if (S != NULL) {
		free(S->Array);
		free(S);
	}
}
/* void PrintStack(Stack S) { // 调试用 
	for (int i = 0; i <= S->TopOfStack; i++) {
		printf("%d ", S->Array[i]);
	} 
	printf("---------------------------------------\n");
} */

int main()
{
	int M, N, orders;
	scanf("%d%d%d", &M, &N, &orders);
    //printf("%d %d %d", M, N, orders); /* 调试 */
    Stack S = CreateStack(M); /* 一次申请栈 */ 
	ElementType sin[N], sout[N];
	/* 设置入栈序列 */
	for (int i = 0; i < N; i++) sin[i] = i + 1; 
	
	while (orders--) {
		/* 设置出栈序列 */
		for (int i = 0; i < N; i++) scanf("%d", &sout[i]);
		/* 关键点 two pointers 分别指向栈和出栈序列 */
		int i = 0, j = 0;
		for (i = 0; i < N; i++) {			
			Push(sin[i], S); /* 按入栈序列1,2,...,N入栈 */
			// PrintStack(S);  /* 调试 */
			/* 栈非空且栈顶元素等于出栈序列的元素时 */  
			while (!IsEmpty(S) && Top(S) == sout[j]) {
				Pop(S);  /* 弹出该栈顶元素 */
				j++;     /* 当对应j位出栈序列的元素后检查下一个元素是否对应 */
			}
			/* 栈满(没有弹出对应于出栈序列的元素), 表示此限定长度的栈无法该出栈序列 */
			if (IsFull(S)) break; 
		}
		if (i == j) printf("YES\n"); /* 出栈序列合法 */ 
		else printf("NO\n");         /* 出栈序列不合法 */ 
		MakeEmpty(S);                /* 清空栈 */	
	}
	DisposeStack(S); /* 一次释放栈空间 */
	return 0;
}

这三道二叉树的题目训练建树(静态结构体数组/指针)、遍历树(先序/中序/后序/层序)的基本功

03-树1 树的同构 (25 分)

给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的,因为我们把其中一棵树的结点A、B、G的左右孩子互换后,就得到另外一棵树。而图2就不是同构的。
图1
在这里插入图片描述

图2
在这里插入图片描述
现给定两棵树,请你判断它们是否是同构的。

  • 输入格式:
    输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数N (≤10),即该树的结点数(此时假设结点从0到N−1编号);随后N行,第i行对应编号第i个结点,给出该结点中存储的1个英文大写字母、其左孩子结点的编号、右孩子结点的编号。如果孩子结点为空,则在相应位置上给出“-”。给出的数据间用一个空格分隔。注意:题目保证每个结点中存储的字母是不同的
  • 输出格式:
    如果两棵树是同构的,输出“Yes”,否则输出“No”。
  • 输入样例1(对应图1):
    8
    A 1 2
    B 3 4
    C 5 -
    D - -
    E 6 -
    G 7 -
    F - -
    H - -
    8
    G - 4
    B 7 6
    F - -
    A 5 1
    H - -
    C 0 -
    D - -
    E 2 -
    
  • 输出样例1:
    Yes
    
  • 输入样例2(对应图2):
    8
    B 5 7
    F - -
    A 0 3
    C 6 -
    H - -
    D - -
    G 4 -
    E 1 -
    8
    D 6 -
    B 5 -
    E - -
    H - -
    C 0 2
    G - 3
    F - -
    A 1 4
    
  • 输出样例2:
    No
    

给出的结点顺序不一定从根结点开始。不过也无所谓,这里使用的是静态结构体数组,可以像完全二叉树一样按根结点左右结点的顺序排,也可以顺便放,只要找到根结点就可以沿着next下标读取数据

检查同构是用递归函数,先检查两棵树是否是空树,再检查两棵树是否有一棵是空树,不然两棵树都非空,则检查它们的结点值,相同的话,就先递归检查T1左子树和T2左子树、T1右子树和T2右子树是否同构,不同的话就再递归检查T1左子树和T2右子树、T1右子树和T2左子树是否同构。这是本题的关键点。

#include <bits/stdc++.h>
#define Null -1
const int MaxSize = 10;
typedef int Tree;

typedef struct treeNode {
	char data;
	Tree left, right;
} BTreeNode;
BTreeNode T1[MaxSize], T2[MaxSize];

Tree BuildTree(BTreeNode *T);
bool isMorph(Tree R1, Tree R2) ;
void printTree(BTreeNode *T, Tree R) {
	if (R != Null) {
		printf("%c \n", T[R].data);
		printTree(T, T[R].left);
		printTree(T, T[R].right);
	}
}

Tree BuildTree(BTreeNode *T) { 
	int n, i;  //非链表顺序存储
	char cl, cr;
	Tree root;
	scanf("%d\n", &n); //n非负整数 
	if (n) {
        int checked[n]; 
		for (i = 0; i < n; i++) checked[i] = 0;
		for (i = 0; i < n; i++) {
			scanf("%c %c %c\n", &T[i].data, &cl, &cr);
			if (cl != '-') {
				T[i].left = cl - '0';
				checked[T[i].left] = 1; //表示该结点被指向了 
			} else T[i].left = Null; 
			if (cr != '-') {
				T[i].right = cr - '0';
				checked[T[i].right] = 1; 
			} else T[i].right = Null; 
		}
		for (i = 0; i < n; i++) {//没有被指向的结点就是根结点 
			if (!checked[i]) break;
		}
		root = i;
	} else root = Null;
	return root; //返回根结点的位置 
}

bool isMorph(Tree R1, Tree R2) {
	bool flag;	
	//都空树
	if (R1 == Null && R2 == Null) flag = true; 
	//某一棵为空 
	else if (R1 == Null || R2 == Null) flag = false; 
	else {	
		 /* compare the data of root */
		if (T1[R1].data != T2[R2].data) flag = false;
		else {
		/* compare the left and right subtree */
		    if (isMorph(T1[R1].left, T2[R2].left) && 
		        isMorph(T1[R1].right, T2[R2].right))
		   	    flag = true;	
	        else if (isMorph(T1[R1].left, T2[R2].right) && 
	                 isMorph(T1[R1].right, T2[R2].left))
       	        flag = true;
    		else 
  			    flag = false;
		}
	}
    return flag;
} 

int main() {	
	Tree R1 = BuildTree(T1), R2 = BuildTree(T2); //建二叉树
	if (isMorph(R1, R2)) printf("Yes\n");
	else printf("No\n");
	return 0;
}

03-树2 List Leaves (25 分)

Given a tree, you are supposed to list all the leaves in the order of top down, and left to right.

  • Input Specification:
    Each input file contains one test case. For each case, the first line gives a positive integer N (≤10) which is the total number of nodes in the tree – and hence the nodes are numbered from 0 to N−1. Then N lines follow, each corresponds to a node, and gives the indices of the left and right children of the node. If the child does not exist, a “-” will be put at the position. Any pair of children are separated by a space.
  • Output Specification:
    For each test case, print in one line all the leaves' indices in the order of top down, and left to right. There must be exactly one space between any adjacent numbers, and no extra space at the end of the line.
  • Sample Input:
    8
    1 -
    - -
    0 -
    2 7
    - -
    - -
    5 -
    4 6
    
  • Sample Output:
    4 1 5
    

与上面的一题有点相似,这里都采用结构体数组,静态二叉链表。然后按照从左到右从上到下列出叶子结点,就是改一下层序遍历的代码。另外,为了满足输出的要求,用vector存储叶子结点的编号

#include <bits/stdc++.h>
using namespace std; 
#define Null -1
typedef int Tree;
const int maxn = 10;

typedef struct BTreeNode {
    int data;
    Tree left, right; 
} BTreeNode;
BTreeNode T[maxn];

Tree CreateTree(BTreeNode *T) {
	Tree root;
	int n;
	scanf("%d\n", &n);
	if (n) {
		char cl, cr;
		int checked[n]; //表示结点是否有父结点 
		for (int i = 0; i < n; i++) checked[i] = 0;
		for (int i = 0; i < n; i++) {
			T[i].data = i; //树结点的编号0到N-1 
			scanf("%c %c\n", &cl, &cr);
			if (cl != '-') {
			   T[i].left = cl - '0';
			   checked[T[i].left] = 1; 
			} else T[i].left = Null;
			if (cr != '-') {
			   T[i].right = cr - '0';
			   checked[T[i].right] = 1; 
			} else T[i].right = Null;
		}
		int j = 0;
		while (j < n && checked[j] != 0) j++;
		root = j; //找到根结点 
	} else root = -1;
	return root; 
}

void ListLeaves(Tree root) { //层序遍历 
	queue<int> q; //队列里面存储树结点编号 
	q.push(root); //根结点入队 
	vector<int> vi; //存储叶子结点编号 
	while (!q.empty()) {
		Tree now = q.front(); //取队首元素
		q.pop(); 
		if (T[now].left == Null && T[now].right == Null) {
			vi.push_back(T[now].data);  
		} 
		if (T[now].left != Null) q.push(T[now].left); //左子树非空 
		if (T[now].right != Null) q.push(T[now].right); //右子树非空 
	}
	for (int i = 0; i < vi.size(); i++) {
		if (i > 0) printf(" ");
		printf("%d", vi[i]);
	}
}

int main() {
	Tree root = CreateTree(T);
	ListLeaves(root);
	return 0;
}

★★★ 03-树3 Tree Traversals Again (25 分)

An inorder binary tree traversal can be implemented in a non-recursive way with a stack. For example, suppose that when a 6-node binary tree (with the keys numbered from 1 to 6) is traversed, the stack operations are: push(1); push(2); push(3); pop(); pop(); push(4); pop(); pop(); push(5); push(6); pop(); pop(). Then a unique binary tree (shown in Figure 1) can be generated from this sequence of operations. Your task is to give the postorder traversal sequence of this tree.

  • Figure 1
    在这里插入图片描述
  • Input Specification:
    Each input file contains one test case. For each case, the first line contains a positive integer N (≤30) which is the total number of nodes in a tree (and hence the nodes are numbered from 1 to N). Then 2N lines follow, each describes a stack operation in the format: “Push X” where X is the index of the node being pushed onto the stack; or “Pop” meaning to pop one node from the stack.
  • Output Specification:
    For each test case, print the postorder traversal sequence of the corresponding tree in one line. A solution is guaranteed to exist. All the numbers must be separated by exactly one space, and there must be no extra space at the end of the line.
  • Sample Input:
    6
    Push 1
    Push 2
    Push 3
    Pop
    Pop
    Push 4
    Pop
    Pop
    Push 5
    Push 6
    Pop
    Pop
    
  • Sample Output:
    3 4 2 6 5 1
    

这道题的基础在于如何表示和建立一棵树,关键在于了解使用栈非递归地先序/中序/后序遍历二叉树,使用队列非递归层序遍历二叉树,然后最重要的也是经常考的,就是从①中序与先序、②中序与后序、③中序与层序序列恢复一棵二叉树

#include <bits/stdc++.h>
using namespace std;
const int MaxSize = 35;

struct BTreeNode {
    int data;
    struct BTreeNode *left;
	struct BTreeNode *right;
};
BTreeNode *newNode(int v) {
    BTreeNode *Node = new BTreeNode;
    Node->data = v;
    Node->left = Node->right = NULL;
    return Node; //返回新建结点的地址 
}

int n;
int preSqu[35], len1 = 0, inSqu[35], len2 = 0;
void Init() { //从输入的命令中得到先序和中序序列
    stack<int> s;
    char order[10]; int temp;
    for (int i = 0; i < 2 * n; i++) {
        scanf("%s", order);
        if (order[1] == 'u') { //push数据即先序遍历访问的时候
            scanf("%d", &temp);
            preSqu[len1++] = temp;
            s.push(temp);
        } else if (order[1] == 'o') { //pop数据即是中序序列访问的时候
            temp = s.top(); s.pop();
            inSqu[len2++] = temp;
        }
    }
}

BTreeNode* InorderCreateTree(int preL, int preR, int inL, int inR) {
	if (preL > preR) { //空则直接返回 
		return NULL;
	}
	BTreeNode* root = newNode(preSqu[preL]);
	int k; 
	for (k = inL; k <= inR; k++) { //在中序序列中找到根结点 
		if (inSqu[k] == preSqu[preL]) break;
	} 
	int numLeft = k - inL; //左子树的结点个数 
	//左子树的先序区间[preL+1, preL+numLeft], 中序区间[inL, k-1]
	//返回左子树的根结点地址, 赋值给根结点的左指针 
	root->left = InorderCreateTree(preL+1, preL+numLeft, inL, k-1);
	//右子树的先序区间[preL+numLeft+1, preR], 中序区间[k+1, inR]
	//返回左子树的根结点地址, 赋值给根结点的左指针 
	root->right = InorderCreateTree(preL+numLeft+1, preR, k+1, inR);
	
	return root; //返回根结点地址 
}

void Postorder(BTreeNode *root) {
	if (root) {
		Postorder(root->left);
		Postorder(root->right);
		printf(--n ? "%d " : "%d", root->data);
	}	
}

int main() {
    scanf("%d", &n);
    Init();
    BTreeNode *T = InorderCreateTree(0, len1 - 1, 0, len2 - 1);
    Postorder(T);
    return 0;
}

这几道题与二叉搜索树、平衡树、完全二叉树有关,二叉搜索树的删除和平衡树的代码写起来有点复杂,但是必须掌握。

04-树4 是否同一棵二叉搜索树 (25 分)

给定一个插入序列就可以唯一确定一棵二叉搜索树。然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到。例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结果。于是对于输入的各种插入序列,你需要判断它们是否能生成一样的二叉搜索树

  • 输入格式:
    输入包含若干组测试数据。每组数据的第1行给出两个正整数N (≤10)和L,分别是每个序列插入元素的个数和需要检查的序列个数。第2行给出N个以空格分隔的正整数,作为初始插入序列。最后L行,每行给出N个插入的元素,属于L个需要检查的序列。
    简单起见,我们保证每个插入序列都是1到N的一个排列。当读到N为0时,标志输入结束,这组数据不要处理。
  • 输出格式:
    对每一组需要检查的序列,如果其生成的二叉搜索树跟对应的初始序列生成的一样,输出“Yes”,否则输出“No”。
  • 输入样例:
    4 2
    3 1 4 2
    3 4 1 2
    3 2 4 1
    2 1
    2 1
    1 2
    0
    
  • 输出样例:
    Yes
    No
    No
    

这一题有多种方法:①全部序列都建树的方法,代码如下。②初始序列建树,其余序列不建树,初始二叉搜索树与其他序列直接比较;③全都不建树。而且二叉搜索树和插入、建立、删除等方法的实现也有多种版本

简单起见,我先使用了第一种方法。

/* C语言版本 */
node* newNode(ElemType v) {
	node *Node = (node *)malloc(sizeof(struct node));
	Node->data = v;
	Node->left = Node->right = NULL;
	return Node;
}
/* 只用指针, 有返回值的插入版本 */
BST Insert(BST root, ElementType X) {
	if(root == NULL){
		root = newNode(X);
	} else {
		if(item > root->Data)
			root->Right = Insert(root->Right, item);
		else if(item < root->Data)
			root->Left = Insert(root->Left, item);
	} /* root指向每棵子树的根结点, 根又会发生变化, 因此在最后需要返回 */
	return root; /* 一定要返回! */
}
BST Create(int n) {
	node *root = NULL; //新建根结点root
	ElemType data;
	for (int i = 0; i < n; i++) {
		scanf("%d", &data); //插入函数不一定要返回值 
		root = Insert(root, data);
	} 
	return root;
}

下面的MakeEmpty清空回收函数可以不写,这里只是为了代码的完整。

/* C++版本 */
#include <bits/stdc++.h>
typedef int ElemType;
typedef struct node {
	ElemType data;
	node *left, *right;
} *BST; //二叉搜索树 

node* newNode(ElemType v) {
	node *Node = new node;
	Node->data = v;
	Node->left = Node->right = NULL;
	return Node;
}
/* 指针引用, 无返回值的插入版本 */
void Insert(BST &root, ElemType X) {
	if (root == NULL) { //空树, 插入位置 
		root = newNode(X);
		return; 
	} else {
		if (X > root->data) Insert(root->right, X);
		else if (X < root->data) Insert(root->left, X);
		/* else X is in the tree, do nothing */ 
	} 
}
BST Create(int n) {
	node *root = NULL; //新建根结点root
	ElemType data;
	for (int i = 0; i < n; i++) {
		scanf("%d", &data); //插入函数不一定要返回值 
		Insert(root, data);
	} 
	return root;
}
BST MakeEmpty(BST root) { //有返回值
	if (root) {
		MakeEmpty(root->left);
		MakeEmpty(root->right);
		free(root); //后序清空 
	} 
	return NULL;  //返回空树 
}

bool isEqualTree(BST t1, BST t2) {
	bool flag;
	if (t1 == NULL && t2 == NULL) flag = true;
	else if (t1 == NULL || t2 == NULL) flag = false;
	else { //compare the value of roots and subtrees
		if (t1->data == t2->data && isEqualTree(t1->left, t2->left) 
		    && isEqualTree(t1->right, t2->right)) 
		   flag = true;
        else flag = false;
	}   
	return flag;
} 

/* 方法: 每个序列都建树, 将之分别与标准二叉搜索树比较 */
int main() {
	int N, L;
	BST templateTree, compareTree;
	while (scanf("%d", &N) != EOF && N != 0) {
		scanf("%d", &L);
		/* build template tree */
		templateTree = Create(N);
		/* build compare tree and print the result */ 
		while (L--) {
			compareTree = Create(N);
			/* do the compare */
			if (isEqualTree(templateTree, compareTree)) 
				printf("Yes\n");
            else printf("No\n");
		} 
		templateTree = MakeEmpty(templateTree);
		compareTree = MakeEmpty(compareTree); //清空 
	}
	return 0;
} 

04-树5 Root of AVL Tree (25 分)

04-树6 Complete Binary Search Tree (30 分)

A Binary Search Tree (BST) is recursively defined as a binary tree which has the following properties:

The left subtree of a node contains only nodes with keys less than the node’s key.

The right subtree of a node contains only nodes with keys greater than or equal to the node’s key.

Both the left and right subtrees must also be binary search trees.

A Complete Binary Tree (CBT) is a tree that is completely filled, with the possible exception of the bottom level, which is filled from left to right.

Now given a sequence of distinct non-negative integer keys, a unique BST can be constructed if it is required that the tree must also be a CBT. You are supposed to output the level order traversal sequence of this BST.
Input Specification:

Each input file contains one test case. For each case, the first line contains a positive integer N (≤1000). Then N distinct non-negative integer keys are given in the next line. All the numbers in a line are separated by a space and are no greater than 2000.
Output Specification:

For each test case, print in one line the level order traversal sequence of the corresponding complete binary search tree. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.
Sample Input:

10
1 2 3 4 5 6 7 8 9 0

Sample Output:

6 3 8 1 5 7 9 0 2 4

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, number[maxn], cbt[maxn], k = 0;
void inorder(int root) {
    if(root > n) {
        return ;
    }
    inorder(root * 2);
    cbt[root] = number[k++];
    inorder(root * 2 + 1);
}
int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) 
        scanf("%d", &number[i]); //以完全二叉树的数组形式存储
    sort(number, number + n); //排序, 即为二叉搜索树的中序序列
    inorder(1); 
    for(int i = 1; i <= n; i++) {
        printf("%d", cbt[i]);
        if(i < n) {
            printf(" ");
        }
    }
    return 0;
}

这几道题也是必须掌握的数据结构和算法:堆(堆排序)、并查集。以及哈夫曼编码。是树的知识的延展。

05-树7 堆中的路径 (25 分)

将一系列给定数字插入一个初始为空的小顶堆H[]。随后对任意给定的下标i,打印从H[i]到根结点的路径。

  • 输入格式:
    每组测试第1行包含2个正整数N和M(≤1000),分别是插入元素的个数、以及需要打印的路径条数。下一行给出区间[-10000, 10000]内的N个要被插入一个初始为空的小顶堆的整数。最后一行给出M个下标
  • 输出格式:
    对输入中给出的每个下标i,在一行中输出从H[i]到根结点的路径上的数据。数字间以1个空格分隔,行末不得有多余空格。
  • 输入样例:
    5 3
    46 23 26 24 10
    5 4 3
    
  • 输出样例:
    24 23 10
    46 23 10
    26 10
    

建堆的方法可以是每读入一个就插入一个;也可以是传入一个数组序列,然后从N/2的位置开始下滤调整。如果有堆排序的话,一定要额外实现一个下滤函数。上滤函数一般都写在Insert函数里面,不太会被调用到。

从子结点到父结点,如果堆从1开始则是下标i/2;堆从零开始则是(i-1)/2。从父结点到左右结点,堆从1开始则是2*i、2*i+1;从0开始则是2*i+1、2*i+2。需要注意这些细小的区别。

#include <bits/stdc++.h>
const int maxn = 1010;
const int MINH = -10001;
int H[maxn], size;

void Insert(int x) {
    int i; //将元素x插入优先队列小顶堆 
	for (i = ++size; H[i / 2] > x; i /= 2) 
		H[i] = H[i / 2];
	H[i] = x;
}

void CreateHeap(int N) {
	size = 0;
	int t;
	H[0] = MINH; /* 设置岗哨 */
	for (int i = 0; i < N; i++) {
		scanf("%d", &t);
		Insert(t); 
	}
}
int main() {
    int n, m, k;
    scanf("%d%d", &n, &m);
	CreateHeap(n); //根据输入序列建堆 
	for (int i = 0; i < m; i++) {
		scanf("%d", &k);
		printf("%d", H[k]); //打印到根的路径 
		while (k > 1) {
			k /= 2;
			printf(" %d", H[k]); 
		}
		printf("\n");
	}
    return 0;
}

★★ 05-树8 File Transfer (25 分)

We have a network of computers and a list of bi-directional connections. Each of these connections allows a file transfer from one computer to another. Is it possible to send a file from any computer on the network to any other?

  • Input Specification:
    Each input file contains one test case. For each test case, the first line contains N (2≤N≤10​4​​), the total number of computers in a network. Each computer in the network is then represented by a positive integer between 1 and N. Then in the following lines, the input is given in the format:
    I c1 c2  
    
    where I stands for inputting a connection between c1 and c2; or
    C c1 c2    
    
    where C stands for checking if it is possible to transfer files between c1 and c2; or
    S
    
    where S stands for stopping this case.
  • Output Specification:
    For each C case, print in one line the word “yes” or “no” if it is possible or impossible to transfer files between c1 and c2, respectively. At the end of each case, print in one line “The network is connected.” if there is a path between any pair of computers; or “There are k components.” where k is the number of connected components in this network.
  • Sample Input 1:
    5
    C 3 2
    I 3 2
    C 1 5
    I 4 5
    I 2 4
    C 3 5
    S
    
  • Sample Output 1:
    no
    no
    yes
    There are 2 components.
    
  • Sample Input 2:
    5
    C 3 2
    I 3 2
    C 1 5
    I 4 5
    I 2 4
    C 3 5
    I 1 3
    C 1 5
    S
    
  • Sample Output 2:
    no
    no
    yes
    yes
    The network is connected.
    

这道题必须使用按秩归并(大小)和路径压缩,不然有几个测试点会运行超时。这是我写的代码,和算法笔记上面的还是有一些差别的。

#include <cstdio>
const int maxn = 10010;
typedef int ElemType; /* 默认元素可以用非负整数表示 */
typedef int SetName;  /* 默认用根结点的下标作为集合名称 */
typedef ElemType DisjSet[maxn]; //不相交集 
DisjSet conn;
int N;

void Initialize(int N) {
	for (int i = 0; i < N; i++) conn[i] = -1; 
}

SetName Find(ElemType X) { //递归查找和路径压缩 
	if (conn[X] < 0) {
		return X;
	} else {
	    return conn[X] = Find(conn[X]); //追溯父结点的位置		
	}
}

void SetUnion(SetName root1, SetName root2) { //按秩(大小)归并 
	if (conn[root1] < conn[root2]) { //1集合规模大于2集合 
		conn[root1] += conn[root2];
		conn[root2] = root1;
	} else { //2集合规模大于1集合 
		conn[root2] += conn[root1];
		conn[root1] = root2;
	}
}

void InputConnection() {
	ElemType s1, s2;
	scanf("%d%d\n", &s1, &s2); //编号从1到N, 因此减1
	SetName r1 = Find(s1 - 1), r2 = Find(s2 - 1);
	if (r1 != r2) {
		SetUnion(r1, r2); 
	}	
}

void CheckConnection() {
	ElemType s1, s2;
	scanf("%d%d\n", &s1, &s2);
	SetName r1 = Find(s1 - 1), r2 = Find(s2 - 1);
	if (r1 == r2) printf("yes\n");
	else printf("no\n");
} 

void CheckNetwork() {
	int i, counter = 0;
	for (int i = 0; i < N; i++) {
		if (conn[i] < 0) counter++;
	}
	if (counter == 1) printf("The network is connected.\n");
	else printf("There are %d components.\n", counter);
}

int main() {
	scanf("%d\n", &N);
	Initialize(N);
	char c;
	do {
	   	scanf("%c", &c);
		switch (c) {
			case 'I': InputConnection(); break;
			case 'C': CheckConnection(); break;
			case 'S': CheckNetwork(); break;
		}
	} while (c != 'S');
	return 0;
} 

05-树9 Huffman Codes (30 分)

In 1953, David A. Huffman published his paper “A Method for the Construction of Minimum-Redundancy Codes”, and hence printed his name in the history of computer science. As a professor who gives the final exam problem on Huffman codes, I am encountering a big problem: the Huffman codes are NOT unique. For example, given a string “aaaxuaxz”, we can observe that the frequencies of the characters ‘a’, ‘x’, ‘u’ and ‘z’ are 4, 2, 1 and 1, respectively. We may either encode the symbols as {‘a’=0, ‘x’=10, ‘u’=110, ‘z’=111}, or in another way as {‘a’=1, ‘x’=01, ‘u’=001, ‘z’=000}, both compress the string into 14 bits. Another set of code can be given as {‘a’=0, ‘x’=11, ‘u’=100, ‘z’=101}, but {‘a’=0, ‘x’=01, ‘u’=011, ‘z’=001} is NOT correct since “aaaxuaxz” and “aazuaxax” can both be decoded from the code 00001011001001. The students are submitting all kinds of codes, and I need a computer program to help me determine which ones are correct and which ones are not.

  • Input Specification:
    Each input file contains one test case. For each case, the first line gives an integer N (2≤N≤63), then followed by a line that contains all the N distinct characters and their frequencies in the following format:
    c[1] f[1] c[2] f[2] ... c[N] f[N]
    
    where c[i] is a character chosen from {'0' - '9', 'a' - 'z', 'A' - 'Z', '_'}, and f[i] is the frequency of c[i] and is an integer no more than 1000. The next line gives a positive integer M (≤1000), then followed by M student submissions. Each student submission consists of N lines, each in the format:
    c[i] code[i]
    
    where c[i] is the i-th character and code[i] is an non-empty string of no more than 63 '0’s and '1’s.
  • Output Specification:
    For each test case, print in each line either “Yes” if the student’s submission is correct, or “No” if not.
  • Note: The optimal solution is not necessarily generated by Huffman algorithm. Any prefix code with code length being optimal is considered correct.
  • Sample Input:
    7
    A 1 B 1 C 1 D 3 E 3 F 6 G 6
    4
    A 00000
    B 00001
    C 0001
    D 001
    E 01
    F 10
    G 11
    A 01010
    B 01011
    C 0100
    D 011
    E 10
    F 11
    G 00
    A 000
    B 001
    C 010
    D 011
    E 100
    F 101
    G 110
    A 00000
    B 00001
    C 0001
    D 001
    E 00
    F 10
    G 11
    
  • Sample Output:
    Yes
    Yes
    No
    No
    


06-图1 列出连通集 (25 分)

给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发按编号递增的顺序访问邻接点

  • 输入格式:输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。
  • 输出格式:
    按照"{ v​1​​ v​2​​ … v​k​​ }"的格式,每行输出一个连通集先输出DFS的结果,再输出BFS的结果
  • 输入样例:
    8 6
    0 7
    0 1
    2 0
    4 1
    2 4
    3 5
    
  • 输出样例:
    { 0 1 4 2 7 }
    { 3 5 }
    { 6 }
    { 0 1 2 7 4 }
    { 3 5 }
    { 6 }
    

写邻接矩阵和BFS都比DFS与邻接表麻烦一点。总之,对于图要养成一个固定的存图形式。比如需要遍历边的题我用vector<pair, pair> E存边,不带权图邻接表用vector<int> E[maxn],带权图邻接表用vector<pair<int, int> E[maxn](pair的first是边的终点,second是边权)类似的方式。

#include <cstdio>
#include <queue>
#include <vector> 
using namespace std;
/* 邻接矩阵版本 */
const int maxv = 50; 
const int inf = 1000;
int nv, ne, G[maxv][maxv];
bool vis[maxv] = {false}, inq[maxv] = {false};

void CreateGraph() {
	int u, v;
	for (int i = 0; i < nv; i++) 
		for (int j = 0; j < nv; j++) 
			G[i][j] = inf;
	for (int i = 0; i < ne; i++) {
		scanf("%d%d", &u, &v); //0到N-1编号 
		G[u][v] = 1; //无向图 
		G[v][u] = 1; 
	}
}

vector<int> comp;
void DFS(int u) {
	vis[u] = true;
	comp.push_back(u);
	for (int v = 0; v < nv; v++) {
		if (vis[v] == false && G[u][v] != inf) {
			DFS(v);
		}
	} 
}
void DFSTrave() {
	for (int i = 0; i < nv; i++) {
		if (vis[i] == false) {
			comp.clear(); 
			DFS(i);
			printf("{");
			for (int j = 0; j < comp.size(); j++) { 
				printf(" %d", comp[j]);
			} 
			printf(" }\n");
		}
	}
}

void BFS(int u) {
	queue<int> q;
	q.push(u);
	inq[u] = true; //u已经加入队列
	while (!q.empty()) {
		int u = q.front(); q.pop();
	    comp.push_back(u);
		for (int v = 0; v < nv; v++) { //如果u的邻接点v未曾加入过队列 
			if (inq[v] == false && G[u][v] != inf) {
				q.push(v); 
				inq[v] = true; //标记v已经加入过队列 
			}
		}
	} 
}
void BFSTrave() {
	for (int u = 0; u < nv; u++) {
		if (inq[u] == false) { //u未曾加入过队列 
			comp.clear(); 
			BFS(u); //遍历u在的连通块 
			printf("{");
			for (int j = 0; j < comp.size(); j++) { 
				printf(" %d", comp[j]);
			} 
			printf(" }\n");
		}
	} 
}

int main() {
	scanf("%d%d", &nv, &ne);
	CreateGraph();
	DFSTrave();
	BFSTrave();
	return 0;
}

06-图3 六度空间 (30 分)

“六度空间”理论又称作“六度分隔(Six Degrees of Separation)”理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”如图1所示。
在这里插入图片描述
“六度空间”理论虽然得到广泛的认同,并且正在得到越来越多的应用。但是数十年来,试图验证这个理论始终是许多社会学家努力追求的目标。然而由于历史的原因,这样的研究具有太大的局限性和困难。随着当代人的联络主要依赖于电话、短信、微信以及因特网上即时通信等工具,能够体现社交网络关系的一手数据已经逐渐使得“六度空间”理论的验证成为可能。

假如给你一个社交网络图,请你对每个节点计算符合“六度空间”理论的结点占结点总数的百分比

  • 输入格式:
    输入第1行给出两个正整数,分别表示社交网络图的结点数N(1<N≤10​3​​,表示人数)、边数M(≤33×N,表示社交关系数)。随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个结点的编号(节点从1到N编号)。
  • 输出格式:
    对每个结点输出与该结点距离不超过6的结点数占结点总数的百分比,精确到小数点后2位。每个结节点输出一行,格式为“结点编号:(空格)百分比%”。
  • 输入样例:
    10 9
    1 2
    2 3
    3 4
    4 5
    5 6
    6 7
    7 8
    8 9
    9 10
    
  • 输出样例:
    1: 70.00%
    2: 80.00%
    3: 90.00%
    4: 100.00%
    5: 100.00%
    6: 100.00%
    7: 100.00%
    8: 90.00%
    9: 80.00%
    10: 70.00%
    

无向图。以下用邻接表。BFS遍历,对每个结点尽可能地访问其连通块中不超过六层的结点

#include <bits/stdc++.h>
using namespace std;
const int maxv = 1010;
struct node {
	int v, layer; //顶点编号, 顶点层号 
	node(int _v, int _l): v(_v), layer(_l) { }
};
vector<node> Adj[maxv]; //结点从1到N编号
bool inq[maxv] = {false};
int nums[maxv] = {0}; //nums[i]表示与结点i距离不超过6的结点数, 包括自己 
int n, m;

void BFS(int u) {
    queue<node> q;
    q.push(node(u, 0)); //将初始点入队
	inq[u] = true; 
	while (!q.empty()) {
		node top = q.front(); q.pop();
		int v = top.v; //队首顶点的编号 
		if (top.layer <= 6) nums[u]++; 
		else break; //超过六度退出 
		for (int i = 0; i < Adj[v].size(); i++) {
			node next = Adj[v][i]; //从v出发能到达的顶点next
			next.layer = top.layer + 1;
			if (inq[next.v] == false) {
				q.push(next); //将k入队 
				inq[next.v] = true; //标记v已加入队列 
			}
		}
	} 
}

void BFSTrave() {
    fill(nums, nums + maxv, 0); 
	for (int i = 1; i <= n; i++) {
		fill(inq, inq + maxv, 0); //每一次将之置0 
		BFS(i); //对每个结点输出与该结点距离不超过6的结点数占结点总数的百分比
		printf("%d: %.2lf%%\n", i, nums[i] * 100.0 / n);
	} 
}

int main() {
	int u, v;
	scanf("%d%d", &n, &m);
	for (int i = 0; i < m; i++) {
		scanf("%d%d", &u, &v);
		Adj[u].push_back(node(v, 0));
		Adj[v].push_back(node(u, 0)); //无向图 
	}
	BFSTrave();
	return 0;
}

考察全源最短路算法。

07-图4 哈利·波特的考试 (25 分)

哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。反方向变化的魔咒就是简单地将原来的魔咒倒过来念,例如ahah可以将老鼠变成猫。另外,如果想把猫变成鱼,可以通过念一个直接魔咒lalala,也可以将猫变老鼠、老鼠变鱼的魔咒连起来念:hahahehe。

现在哈利·波特的手里有一本教材,里面列出了所有的变形魔咒和能变的动物。老师允许他自己带一只动物去考场,要考察他把这只动物变成任意一只指定动物的本事。于是他来问你:带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?例如:如果只有猫、鼠、鱼,则显然哈利·波特应该带鼠去,因为鼠变成另外两种动物都只需要念4个字符;而如果带猫去,则至少需要念6个字符才能把猫变成鱼;同理,带鱼去也不是最好的选择。

  • 输入格式:
    输入说明:输入第1行给出两个正整数N (≤100)和M,其中N是考试涉及的动物总数,M是用于直接变形的魔咒条数。为简单起见,我们将动物按1~N编号。随后M行,每行给出了3个正整数,分别是两种动物的编号、以及它们之间变形需要的魔咒的长度(≤100),数字之间用空格分隔。
  • 输出格式:
    输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。
  • 输入样例:
    6 11
    3 4 70
    1 2 1
    5 4 50
    2 6 50
    5 6 60
    1 3 70
    4 6 60
    3 6 80
    5 1 100
    2 4 60
    5 2 80
    
  • 输出样例:
    4 70
    

这就是对每一个结点,都求出其到另外N-1个结点的最短距离,最短距离中最长的那个,就是哈利·波特自己带去的动物所需要的魔咒最长,最难变成的那个动物。然后从这些最长距离中得到最短的魔咒长度,如果这个长度为inf,说明做不到。
由于结点数目只有100,使用floyd算法。

#include <bits/stdc++.h>
using namespace std;
const int maxv = 110, inf = 1000000000;
int G[maxv][maxv];
int n, m;
void floyd() {
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i <= n; i++) {
			if (G[i][k] != inf) {
				for (int j = 1; j <= n; j++) {
					if (G[k][j] != inf && G[i][k] + G[k][j] < G[i][j]) {
						G[i][j] = G[i][k] + G[k][j];
					}
				}
			}
		}
	} 
}

int main() {
	int u, v, w;
	scanf("%d%d", &n, &m);
	fill(G[0], G[0] + maxv * maxv, inf);
	for (int i = 1; i <= n; i++) 
		G[i][i] = 0; //自己到自己为0, 无负环 
	for (int i = 0; i < m; i++) {
		scanf("%d%d%d", &u, &v, &w);
		G[u][v] = G[v][u] = w;
	}
	floyd();
    int mindist = inf, animal = 0;
    //检查每一行里面最大的数字 
	for (int i = 1; i <= n; i++) {
		int max = -1;
		for (int j = 1; j <= n; j++) 
			if (G[i][j] > max) 
				max = G[i][j];
		if (max == inf) {
			printf("0\n");
			return 0;
		}
		if (mindist > max) {
			mindist = max; //找到最长距离最小的动物
			animal = i; 
		}
	} 
	printf("%d %d\n", animal, mindist);
	return 0;
} 

考察Dijkstra最短路算法。这里没有负环或者负值圈,但是有第二标尺,除了最短路外还有判断条件。

07-图6 旅游规划 (25 分)

有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径

  • 输入格式: 输入数据的第1行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。
  • 输出格式: 在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。
  • 输入样例:
    4 5 0 3
    0 1 1 20
    1 3 2 30
    0 3 4 10
    0 2 2 20
    2 3 1 20
    
  • 输出样例:
    3 40
    
#include <bits/stdc++.h>
using namespace std; 
/* dijkstra + 第二标尺最短路 */
const int inf = 1000000000, maxv = 510;
int n, m, s, D; //城市个数 公路条数 出发地编号 目的地编号
int G[maxv][maxv], cost[maxv][maxv]; //0 N-1编号 
int c[maxv], d[maxv]; //数字均为整数且不超过500 
bool vis[maxv] = {false}; 

void Dijkstra(int s) {
	fill(d, d + maxv, inf);  //将整个d数组初始化为inf 
	d[s] = 0; //起点距离自己的距离是0 
	fill(c, c + maxv, inf);  //将整个cost数组初始化化为inf
	c[s] = 0; //起点到达自己的花费为0 
	for (int i = 0; i < n; i++) {
		int u = -1, min = inf;
		for (int j = 0; j < n; j++) { //找到未访问顶点中d[v]最小的 
			if (vis[j] == false && d[j] < min) {
				u = j;
				min = d[j]; 
			}
		}
		if (u == -1) return; //其他的顶点与起点S不连通 
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false && G[u][v] != inf) {
				if (d[u] + G[u][v] < d[v]) { //最短路径和花费 
					d[v] = d[u] + G[u][v];
					c[v] = c[u] + cost[u][v];
				} else if (d[u] + G[u][v] == d[v] && c[u] + cost[u][v] < c[v]) {
					c[v] = c[u] + cost[u][v]; //最短距离相同时看能否使c[v]最优 
				} 
			} 
		}
	}
}

int main() {
	fill(G[0], G[0] + maxv * maxv, inf); //初始化G 
	fill(cost[0], cost[0] + maxv * maxv, inf); //初始化cost 
	scanf("%d%d%d%d", &n, &m, &s, &D);
	int c1, c2, p, spend; //城市1、城市2、高速公路长度、收费额
	for (int i = 0; i < m; i++) { //城市1、城市2、高速公路长度、收费额
		scanf("%d%d%d%d", &c1, &c2, &p, &spend);
		G[c1][c2] = G[c2][c1] = p; //记录公路长度 
		cost[c1][c2] = cost[c2][c1] = spend; //记录收费额 
	} 
	Dijkstra(s);
    //输出路径的长度和收费总额
    printf("%d %d\n", d[D], c[D]);
	return 0;  
} 

考察最小生成树算法。

08-图7 公路村村通 (30 分)

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本

  • 输入格式:
    输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。
  • 输出格式:
    输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。
  • 输入样例:
    6 15
    1 2 5
    1 3 3
    1 4 7
    1 5 4
    1 6 2
    2 3 4
    2 4 6
    2 5 2
    2 6 6
    3 4 6
    3 5 1
    3 6 1
    4 5 10
    4 6 8
    5 6 3
    
  • 输出样例:
    12
    

该条道路直接连通的两个城镇的编号就是结点,该道路改建的预算成本即为边权。这里先使用Kruskal算法(编程更简单),“最短的边一定在MST上面”,使用边集数组排序后依次选择边权最小的边,然后使用并查集判断两个端点是否在不同的连通块中,是否会形成回路。

#include <bits/stdc++.h> 
/* Kruskal + 并查集 + 边集数组 + 排序 */ 
using namespace std;
const int maxv = 1010; //1000个最大城镇 
int n, m; //n个结点和m条边
struct edge { int u, v, w; } E[3 * maxv]; 
bool cmp(edge a, edge b) {
	return a.w < b.w; //边权从小到大排序 
}

int father[maxv]; //并查集
int initialize() { //结点映射为1-N编号 
	for (int i = 1; i <= n; i++) father[i] = -1;
} 
int find(int x) {
	if (father[x] < 0) return x;
	else return father[x] = find(father[x]);
}

void setUnion(int u, int v) {
	if (father[u] < father[v]) {
		father[u] += father[v];
		father[v] = u;
	} else {
		father[v] += father[u];
		father[u] = v;
	}
}

int kruskal() {
	int ans = 0;
	initialize(); //初始化, 开始时每个村庄都是独立的集 
	sort(E + 1, E + 1 + m, cmp); //排序 
	for (int i = 1; i <= m; i++) {
		int a = find(E[i].u); //边的前端点属于哪个集
		int b = find(E[i].v); //边的后端点属于哪个集
		if (a == b) continue; //产生了圈, 丢弃这个边 
		else setUnion(a, b); //如果这两个点不属于同一个集, 没修路
		ans += E[i].w; //修路, 添加权重, 计算MST 
	}
	int comps = 0;
	for (int i = 1; i <= n; i++) {
		if (father[i] < 0) comps++; 
	} 
	if (comps == 1) return ans; //全部点都可以通过候选道路中的生成树连通
	else return -1; 
}
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)  //点的编号从1开始 
		scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].w);
	printf("%d\n", kruskal()); 
	return 0;
}

其实,每一次集合并都又连通了一个点,只要连通了N-1次就可以退出了。如果边太多,连通的话可以提前退出,而无需遍历完整个边集数组。

	else {
	    setUnion(a, b); //如果这两个点不属于同一个集, 没修路
	    ans += E[i].w; //修路, 添加权重, 计算MST 
	    cnt++; 
	    if (cnt >= n - 1) return ans;
	}	
	return -1; 
}

使用prim算法的代码如下,prim算法的思想是“最近的邻居一定在MST”中,与Dijkstra的唯一区别在于最短距离是顶点Vi针对起点s还是集合S。而且对于prim来说如果仅仅求最小边权之和,可以随意指定一个顶点为初始点。不过prim对于稠密图效果比较好。
这下面的两版代码分别使用了邻接矩阵和邻接表,没有用优先队列优化,因此不是作为模板的最好选择

#include <bits/stdc++.h> 
/* Kruskal + 邻接矩阵 + 集合S */ 
using namespace std;

const int inf = 1000000000, maxv = 1010; //1000个最大城镇 
int n, m, G[maxv][maxv], d[maxv]; //n个结点和m条边; 顶点与集合S的最短距离 
bool vis[maxv] = {false}; //标记数组, 标记顶点加入了集合S

int prim() { //默认1号为初始点, 返回MST的边权和 
	fill(d, d + maxv, inf); //将整个d数组赋值为inf
	d[1] = 0; //只有1号结点的到集合S的距离为0
	int ans = 0; //MST的边权和
	for (int i = 0; i < n; i++) { //循环n次
		int u = -1, min = inf; //u使得d[u]最小, min存放最小的d[u]
		for (int j = 1; j <= n; j++) {
			if (vis[j] == false && d[j] < min) {
				u = j;
				min = d[j];
			}
		} 
		//找不到小于inf的d[u], 剩下的顶点和集合S不相通
		if (u == -1) return -1;
		vis[u] = true;
		ans += d[u]; //将与集合S距离最小的边加入最小生成树 
	    for (int v = 1; v <= n; v++) {
	    	//v未访问, u可达v, 以u为中介点可以使得v离集合S更近
			if (vis[v] == false && G[u][v] != inf && G[u][v] < d[v]) {
				d[v] = G[u][v]; //将G[u][v]赋值给d[v] 
			} 
		}
	}
	return ans; //返回MST的边权之和 
} 

int main() {
	scanf("%d%d", &n, &m);
	int u, v, w;
	fill(G[0], G[0] + maxv * maxv, inf); //初始化图G 
	for (int i = 1; i <= m; i++)  {//点的编号从1开始  
		scanf("%d%d%d", &u, &v, &w);
		G[u][v] = G[v][u] = w; //无向图 
	}
	printf("%d\n", prim()); 
	return 0;
}

感觉prim邻接表的代码还有点麻烦,不过和上面邻接矩阵的差别就只有那个更新d[v]的for循环

#include <bits/stdc++.h> 
/* Kruskal + 邻接表 + 集合S */ 
using namespace std;
struct node {
	int v, w; //v为边的目标, w为边权 
	node(int _v, int _w): v(_v), w(_w) { } 
};
const int inf = 1000000000, maxv = 1010; //1000个最大城镇 
int n, m, d[maxv]; //n个结点和m条边; 顶点与集合S的最短距离 
bool vis[maxv] = {false}; //标记数组, 标记顶点加入了集合S
vector<node> Adj[maxv]; //邻接表 

int prim() { //默认1号为初始点, 返回MST的边权和 
	fill(d, d + maxv, inf); //将整个d数组赋值为inf
	d[1] = 0; //只有1号结点的到集合S的距离为0, 其余全部为inf 
	int ans = 0; //MST的边权和
	for (int i = 0; i < n; i++) { //循环n次
		int u = -1, min = inf; //u使得d[u]最小, min存放最小的d[u]
		for (int j = 1; j <= n; j++) {
			if (vis[j] == false && d[j] < min) {
				u = j;
				min = d[j];
			}
		} 
		//找不到小于inf的d[u], 剩下的顶点和集合S不相通
		if (u == -1) return -1;
		vis[u] = true;
		ans += d[u]; //将与集合S距离最小的边加入最小生成树 
	    for (int i = 0; i < Adj[u].size(); i++) { //u可达v
	    	int v = Adj[u][i].v; //通过邻接表直接获得u能达到的顶点v 
	    	//v未访问, 以u为中介点可以使得v离集合S更近
			if (vis[v] == false && Adj[u][i].w < d[v]) {
				d[v] = Adj[u][i].w; //将G[u][v]赋值给d[v] 
			} 
		}
	}
	return ans; //返回MST的边权之和 
} 

int main() {
	scanf("%d%d", &n, &m);
	int u, v, w; 
	for (int i = 1; i <= m; i++)  {//点的编号从1开始  
		scanf("%d%d%d", &u, &v, &w);
		Adj[u].push_back(node(v, w)); //无向图 
		Adj[v].push_back(node(u, w));  
	}
	printf("%d\n", prim()); 
	return 0;
}


09-排序1 排序 (25 分)

给定N个(长整型范围内的)整数,要求输出从小到大排序后的结果。本题旨在测试各种不同的排序算法在各种数据情况下的表现。各组测试数据特点如下:

  • 数据1:只有1个元素;
  • 数据2:11个不相同的整数,测试基本正确性;
  • 数据3:103个随机整数;
  • 数据4:104个随机整数;
  • 数据5:105个随机整数;
  • 数据6:105个顺序整数;
  • 数据7:105个逆序整数;
  • 数据8:105个基本有序的整数;
  • 数据9:105个随机正整数,每个数字不超过1000。
  1. 输入格式:
    输入第一行给出正整数N(≤10​5​​),随后一行给出N个(长整型范围内的)整数,其间以空格分隔。
  2. 输出格式:
    在一行中输出从小到大排序后的结果,数字间以1个空格分隔,行末不得有多余空格。
  • 输入样例:
    11
    4 981 10 -17 0 -20 29 50 8 43 -5
    
  • 输出样例:
    -20 -17 -5 0 4 8 10 29 43 50 981
    

说是长整数,其实写int也可以。这一题里面,我自己先后实现了简单选择排序、冒泡排序、插入排序、希尔排序、堆排序、归并排序和快速排序。尽管实际中很少会遇到自己写排序算法的时候,一般都是调用库函数,但我还是感觉有一点收获。

#include <cstdio>
#include <algorithm>
using namespace std;

void trace(int a[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d", a[i]);
        if (i < n - 1) printf(" ");
    }
    printf("\n");
}

// void BubbleSort(int a[], int n) {
//     for (int i = n - 1; i > 0; i--) {
//         int flag = 1;
//         for (int j = 0; j < i; j++) {
//             if (a[j] > a[j + 1]) {
//                 swap(a[j], a[j + 1]);
//                 flag = 0;
//             }
//         }
//         if (flag) break;
//     }
// }
void InsertionSort(int a[], int n) {
    for (int i = 1; i < n; i++) {
        int temp = a[i], j;
        for (j = i; j > 0 && a[j - 1] > temp; j--) 
            a[j] = a[j - 1];
        a[j] = temp;
    }
}

int main() {
    int n;
    scanf("%d", &n);
    int a[n]; 
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);

    InsertionSort(a, n);
    trace(a, n);
    return 0;
}

10-排序4 统计工龄 (20 分)

给定公司N名员工的工龄,要求按工龄增序输出每个工龄段有多少员工。

  • 输入格式:输入首先给出正整数N(≤10​5​​),即员工总人数;随后给出N个整数,即每个员工的工龄,范围在[0, 50]。
  • 输出格式:按工龄的递增顺序输出每个工龄的员工个数,格式为:“工龄:人数”。每项占一行。如果人数为0则不输出该项。
  • 输入样例:
    8
    10 2 0 5 7 2 5 2
    
  • 输出样例:
    0:1
    2:3
    5:2
    7:1
    10:1
    

整数哈希,递增打印。

#include <cstdio>
int workage[60] = {0}; //每个员工的工龄,范围在[0, 50]

int main() {
    int n, t;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &t);
        workage[t]++;
    }
    for (int i = 0; i <= 50; i++) {
        if (workage[i] != 0) printf("%d:%d\n", i, workage[i]);
    }
    return 0;
}

11-散列1 电话聊天狂人 (25 分)

给定大量手机用户通话记录,找出其中通话次数最多的聊天狂人

  • 输入格式:输入首先给出正整数N(≤10​5​​),为通话记录条数。随后N行,每行给出一条通话记录。简单起见,这里只列出拨出方和接收方的11位数字构成的手机号码,其中以空格分隔。
  • 输出格式:在一行中给出聊天狂人的手机号码及其通话次数,其间以空格分隔。如果这样的人不唯一,则输出狂人中最小的号码及其通话次数,并且附加给出并列狂人的人数
  • 输入样例:
    4
    13005711862 13588625832
    13505711862 13088625832
    13588625832 18087925832
    15005713862 13588625832
    
  • 输出样例:
    13588625832 3
    

这里使用了C++11的标准,auto。不了解的可以看看这两篇文章:
https://blog.csdn.net/qq_42957923/article/details/90107840
https://www.cnblogs.com/KunLunSu/p/7861330.html
同时,map可以自动对第一关键字进行排序,因此我们可以很容易的得到最小的号码。

#include <cstdio>
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    int n; scanf("%d", &n);
    string t1, t2;
    map<string, int> mp;
    for (int i = 0; i < n; i++) {
        cin >> t1 >> t2;
        mp[t1]++;
        mp[t2]++;
    }    
    int max = 0, ans = 0;
    for (auto m : mp) {
        if (m.second > max) {
            t1 = m.first;
            max = m.second;
        } 
    }
    for (auto m : mp) {
        if (m.second == max) ans++;
    }
    if (ans > 1) cout << t1 << " " << max << " " << ans << endl;
    else cout << t1 << " " << max << endl;
    return 0;
}

11-散列3 QQ帐户的申请与登陆 (25 分)

实现QQ新帐户申请和老帐户登陆的简化版功能。最大挑战是:据说现在的QQ号码已经有10位数了。

  • 输入格式:
    输入首先给出一个正整数N(≤10​5​​),随后给出N行指令。每行指令的格式为:“命令符(空格)QQ号码(空格)密码”。其中命令符为“N”(代表New)时表示要新申请一个QQ号,后面是新帐户的号码和密码;命令符为“L”(代表Login)时表示是老帐户登陆,后面是登陆信息。QQ号码为一个不超过10位、但大于1000(据说QQ老总的号码是1001)的整数。密码为不小于6位、不超过16位、且不包含空格的字符串
  • 输出格式: 针对每条指令,给出相应的信息:
    1)若新申请帐户成功,则输出“New: OK”;
    2)若新申请的号码已经存在,则输出“ERROR: Exist”;
    3)若老帐户登陆成功,则输出“Login: OK”;
    4)若老帐户QQ号码不存在,则输出“ERROR: Not Exist”;
    5)若老帐户密码错误,则输出“ERROR: Wrong PW”。
  • 输入样例:
    5
    L 1234567890 [email protected]
    N 1234567890 [email protected]
    N 1234567890 [email protected]
    L 1234567890 myQQ@qq
    L 1234567890 [email protected]
    
  • 输出样例:
    ERROR: Not Exist
    New: OK
    ERROR: Exist
    ERROR: Wrong PW
    Login: OK
    

使用了STL的代码,map也能过,不过时间长一些:

#include <bits/stdc++.h>
using namespace std;

int main() {
	int n; scanf("%d", &n);
	unordered_map<string, int> acc; //账号 
	unordered_map<string, string> psw; //密码
	char c;
	string a, p;
	for (int i = 0; i < n; i++) {
		cin >> c >> a >> p;
		switch (c) {
			case 'N':
				if (acc[a] == 0) {
					acc[a] = 1; //新申请帐户
					psw[a] = p; //存入密码 
					printf("New: OK\n");
				} else printf("ERROR: Exist\n"); //新申请的号码已经存在
				break;
			case 'L': 
				if (acc[a] == 0) printf("ERROR: Not Exist\n"); //老帐户QQ号码不存在
				else if (psw[a] != p) printf("ERROR: Wrong PW\n"); //老帐户密码错误
				else printf("Login: OK\n");
				break;
		}
	} 
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章