目录
前言:
请仔细阅读题目,不保证以后icoding不会改变题目。
同时,希望大家可以自主完成icoding,而不是将其变为icopying,本文仅提供参考,更多的是希望思路受阻的同学可以得到启示。
线性表
1.顺序表 删除指定范围
设计一个高效的算法,从顺序表L中删除所有值介于x和y之间(包括x和y)的所有元素(假设y>=x),要求时间复杂度为O(n),空间复杂度为O(1)。
函数原型如下:void del_x2y(SeqList *L, ElemType x, ElemType y);
相关定义如下:
struct _seqlist{
ElemType elem[MAXSIZE];
int last;
};
typedef struct _seqlist SeqList;
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
void del_x2y(SeqList *L, ElemType x, ElemType y) {
int i = 0, j =0,t =0;
for(i = 0;i <=L->last; i++){
if(L->elem[i] <x || L->elem[i]>y)
{L->elem[j++] = L->elem[i];
t++;
}
}
L->last = t-1 ;
}
2.顺序表 删除重复
编写算法,在一非递减的顺序表L中,删除所有值相等的多余元素。要求时间复杂度为O(n),空间复杂度为O(1)。
函数原型如下:void del_dupnum(SeqList *L)
相关定义如下:
struct _seqlist{
ElemType elem[MAXSIZE];
int last;
};
typedef struct _seqlist SeqList;
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
void del_dupnum(SeqList *L) {
int i=0,j=0;
L->elem[j++] = L->elem[i++];
for(;i<=L->last;i++){
if(L->elem[i]!= L->elem[i-1]){
L->elem[j++]= L->elem[i];
}
}
L->last = j-1;
}
3.顺序表 数据调整
已知顺序表L中的数据元素类型为int。设计算法将其调整为左右两部分,左边的元素(即排在前面的)均为奇数,右边所有元素(即排在后面的)均为偶数,并要求算法的时间复杂度为O(n),空间复杂度为O(1)。
函数原型如下:
void odd_even(SeqList *L);
相关定义如下:
struct _seqlist{
ElemType elem[MAXSIZE];
int last;
};
typedef struct _seqlist SeqList;
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
void odd_even(SeqList *L) {
int i = 0, j=L->last,tmp;
while (i < j) {
if (L->elem[i] % 2 != 1) {
if (L->elem[j] % 2 != 0)//右边为奇数
{
tmp = L->elem[i];
L->elem[i] = L->elem[j];
L->elem[j] = tmp;
j--;
i++;
}
else//右边为偶数
{
j--;
}
}
else
{
i++;
}
}
}
4.链表 删除范围内结点
已知线性表中的元素(整数)以值递增有序排列,并以单链表作存储结构。试写一高效算法,删除表中所有大于mink且小于maxk的元素(若表中存在这样的元素),分析你的算法的时间复杂度。
链表结点定义如下:
struct _lnklist{
ElemType data;
struct _lnklist *next;
};
typedef struct _lnklist Node;
typedef struct _lnklist *LinkList;
函数原型如下:void lnk_del_x2y(LinkList L, ElemType mink, ElemType maxk)
其中L指向链表的头结点。
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
void lnk_del_x2y(LinkList L, ElemType mink, ElemType maxk) {
LinkList prep,cur;
prep = L;
cur = L->next;
while(cur->next != NULL)//边界
{ if(cur->data > mink && cur->data <maxk)
{
prep->next = cur->next;
free(cur);
cur = prep->next;
}
else
{
cur = cur->next;
prep = prep->next;
}
}
if(cur->next == NULL){
if(cur->data > mink && cur->data <maxk)
{prep->next = NULL;}
}
}
5.链表 倒数查找
已知一个带有表头结点的单链表, 假设链表只给出了头指针L。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。
函数原型为:int lnk_search(LinkList L, int k, ElemType* p_ele)
若查找成功,函数通过指针参数 p_ele 返回该结点 data 域的值,此时函数返回 1;否则,函数返回 0。相关定义如下:
struct _lnklist{
ElemType data;
struct _lnklist *next;
};
typedef struct _lnklist Node;
typedef struct _lnklist *LinkList;
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
int lnk_search(LinkList L, int k, ElemType* p_ele) {
int num =0,a;
a = 1;
LinkList last,prep;
prep = L;
last = L;
while(prep->next != NULL){
prep = prep->next;
num++;
}
if(k > num)
{return 0;}
else{
while(a<= (num-k)){
last = last->next;
a++;
}
*p_ele = last->data;
return 1;
}
}
6.链表 合并
设线性表A=(a1, a2,…,am),B=(b1, b2,…,bn),试写一个按下列规则合并A、B为线性表C的算法,使得:
C= (a1, b1,…,am, bm, bm+1, …,bn) 当m≤n时;
或者
C= (a1, b1,…,an, bn, an+1, …,am) 当m>n时。
线性表A、B、C均以单链表作为存储结构,且C表利用A表和B表中的结点空间构成。注意:单链表的长度值m和n均未显式存储。
函数的原型如下:void lnk_merge(LinkList A, LinkList B, LinkList C)
即将A和B合并为C,其中 C 已经被初始化为空单链表
相关定义如下:
struct _lnklist{
ElemType data;
struct _lnklist *next;
};
typedef struct _lnklist Node;
typedef struct _lnklist *LinkList;
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
void lnk_merge(LinkList A, LinkList B, LinkList C) {
Node *x=C;
Node *pa = A->next;
Node *pb = B->next;
for(;;){
int a=1,b=1;
if (pa !=NULL){
Node* tem;
tem=pa;
x->next=tem;
pa=tem->next;
tem->next=NULL;
x=x->next;
a=0;
}
if (pb !=NULL){
Node* tem;
tem=pb;
x->next=tem;
pb=tem->next;
tem->next=NULL;
x=x->next;
b=0;
}
if(a&&b) break;
}
}
队列栈
1.队列 循环链表表示队列
假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素结点(注意不设头指针),请完成下列任务:
1: 队列初始化,成功返回真,否则返回假: bool init_queue(LinkQueue *LQ);
2: 入队列,成功返回真,否则返回假: bool enter_queue(LinkQueue *LQ, ElemType x);
3: 出队列,成功返回真,且*x为出队的值,否则返回假 bool leave_queue(LinkQueue *LQ, ElemType *x);
相关定义如下:
typedef struct _QueueNode {
ElemType data; // 数据域
struct _QueueNode *next; // 指针域
}LinkQueueNode, *LinkQueue;
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
bool init_queue(LinkQueue *LQ)
{
LinkQueueNode *tem;
tem=(LinkQueueNode*)malloc(sizeof(LinkQueueNode));
if(!tem)return false;
(*LQ)=tem;
(*LQ)->next=tem;
return true;
}
bool enter_queue(LinkQueue *LQ, ElemType x)
{
LinkQueueNode *tem;
tem=(LinkQueueNode*)malloc(sizeof(LinkQueueNode));
if(!tem)return false;
tem->data=x;
tem->next=(*LQ)->next;
(*LQ)->next=tem;
(*LQ)=tem;
return true;
}
bool leave_queue(LinkQueue *LQ, ElemType *x)
{
if((*LQ)->next == (*LQ))return false;
LinkQueueNode* head=(*LQ)->next;
LinkQueueNode* pop=head->next;
*x=pop->data;
if(pop->next == head)
(*LQ)=head;
head->next=pop->next;
free(pop);
return true;
}
2.栈 后缀表达式计算
请使用已定义好的栈完成后缀表达式计算:
(1)如果是操作数,直接入栈
(2)如果是操作符op,连续出栈两次,得到操作数x 和 y,计算 x op y,并将结果入栈。
后缀表达式示例如下:
9 3 1 - 3 * + 10 2 / +
13 445 + 51 / 6 -
操作数、操作符之间由空格隔开,操作符有 +,-,*, /, %共 5 种符号,所有操作数都为整型。
栈的定义如下:
#define Stack_Size 50
typedef struct{
ElemType elem[Stack_Size];
int top;
}Stack;
bool push(Stack* S, ElemType x);
bool pop(Stack* S, ElemType *x);
void init_stack(Stack *S);
其中,栈初始化的实现为:
void init_stack(Stack *S){
S->top = -1;
}
需要完成的函数定义为:int compute_reverse_polish_notation(char *str);
函数接收一个字符指针,该指针指向一个字符串形式的后缀表达式,函数返回该表达式的计算结果。
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 请不要删除,否则检查不通过
#include <math.h>
int compute_reverse_polish_notation(char *str)
{
Stack st;
init_stack(&st);
int l=strlen(str);
for(int i=-1;i<l;)
{
char ch=str[++i];
if(ch=='+'||ch=='-'||ch=='*'||ch=='/'||ch=='%')
{
int x,y,cal;
cal=x=y=0;
if(!pop(&st,&y))break;
if(!pop(&st,&x))break;
switch(ch)
{
case '+':
cal=(int)(x+y);
break;
case '-':
cal=(int)(x-y);
break;
case '*':
cal=(int)(x*y);
break;
case '/':
cal=(int)(x/y);
break;
case '%':
cal=(int)(x%y);
break;
}
if(!push(&st,cal))break;
continue;
}
if(ch>='0'&&ch<='9')
{
int sum=0;
for(;ch>='0'&&ch<='9';)
{
sum=sum*10+ch-'0';
ch=str[++i];
}
if(!push(&st,sum))break;
}
}
int ans=0;
pop(&st,&ans);
return ans;
}
字符串
1.串比较
不调用库函数,自己实现字符串的比较操作:该操作当比较的两个字符是都是字母,且两个字符互为大小写(如a和A、e和E)时认为两个字符相同,否则不同,其比较结果按这两个字符的原值确定。函数的返回值规定如下:
返回值 < 0:第一个不匹配的字符在 ptr1 中的值低于 ptr2 中的值
返回值 == 0:两个字符串的内容相等
返回值 > 0:第一个不匹配的字符在 ptr1 中的值大于在 ptr2 中的值
int str_compare(const char* ptr1, const char* ptr2);
#include <stdio.h>
#include <stdlib.h>
#include "dsstring.h" //请不要删除,否则检查不通过
#include <string.h>
int str_compare(const char* ptr1, const char* ptr2) {
while (*ptr1 == *ptr2 || *ptr1 + 32 == *ptr2 || *ptr2 + 32 == *ptr1) {
if (*ptr1 == '\0')
return 0;
(ptr1)++;
(ptr2)++;
}
return (*ptr1 - *ptr2);
}
2.串替换
不调用库函数,自己实现字符串替换操作,函数原型为:
int str_replace(const char *in, char *out, int outlen, const char *oldstr, const char *newstr);
参数说明:
- in, 原始字符串,保持不变
out, 存放替换结果的字符串
outlen,out空间的大小
oldstr,要替换的旧字符串
newstr,替换成的新字符串
函数返回成功替换的次数,即有多少个子串被成功替换
在替换过程中,任何情况下所得字符串(及结束符)不应该超过 outlen,如果某次替换所得字符串的长度超过 outlen,则不进行这次替换操作,整个替换操作结束。如:
原始串为 "aaabbbccc",outlen 为14, oldstr 为 "c",newstr 为 "333" 时,两次替换后得 "aaabbb333333c",此时字符串占用空间为14字节。
如果再进行替换,则会超出 out 所占用的空间,所以停止替换操作。此时函数应该返回 2, out指向的串为 "aaabbb333333c"
再如:原始串为 "aaabbbccc",outlen 为10, oldstr 为 "bb",newstr 为 "123456",进行替换后所得的串长度为14,与结束符一共占 15 个字节,超过outlen的10字节,此时不进行替换,函数应该返回 0。
#include<stdio.h>
#include<stdlib.h>
#include "dsstring.h"
void* Memset(void* s, int c, size_t n)
{
if (NULL == s || n < 0)
return NULL;
char* tmpS = (char*)s;
while (n-- > 0)
*tmpS++ = c;
return s;
}
char* Strncat(char* dest, const char* source, int num)
{
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
while (num && (*dest++ = *source++))
{
num--;
}
return ret;
}
char* Strcat(char* dest, const char* src)
{
char* ret = dest;
//1.找到目的字符串的'\0'
while (*dest != '\0')
{
dest++;
}
//2.追加
while (*dest++ = *src++)
{
;
}
return ret;
}
int Strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
str++;
count++;
}
return count;
}
int Strncmp(const char* str1, const char* str2, int size) {
for (int i = 0; i < size; ++i) {
if (*(str1 + i) > *(str2 + i)) {
return 1;
}
else if (*(str1 + i) < *(str2 + i)) {
return -1;
}
if (*(str1 + i) == 0 || *(str2 + i) == 0) {
break;
}
}
return 0;
}
int str_replace(const char* in, char* out, int outlen, const char* oldstr, const char* newstr) {
Memset(out, 0, outlen);
int count = 0;
for (int i = 0; i < Strlen(in); i++) {
if (!Strncmp(in + i, oldstr, Strlen(oldstr)) && (Strlen(out) + Strlen(newstr) < outlen)) {
//查找到目标字符串
if(Strlen(in) + (Strlen(newstr) - Strlen(oldstr)) * (count + 1) + 1 > outlen){
Strcat(out, in + i);
Strcat(out, "\0");
return count;
}
Strcat(out, newstr);
//把新字符串贴到缓冲字符串里
i += Strlen(oldstr) - 1;
count++;
}
else { //如果没有找到
Strncat(out, in + i, 1);//将该字符写入缓冲字符串数组
}
}
return count;
}
3.块链串
块链串定义如下:
#define BLOCK_SIZE 4 // 可由用户定义的块大小
#define BLS_BLANK '#' // 用于空白处的补齐字符
typedef struct _block {
char ch[BLOCK_SIZE]; //块的数据域
struct _block *next; //块的指针域
} Block;
typedef struct {
Block *head; // 串的头指针
Block *tail; // 串的尾指针
int len; // 串的当前长度
} BLString;
//字符串初始化函数:
void blstr_init(BLString *T) {
T->len = 0;
T->head = NULL;
T->tail = NULL;
}
这些定义已包含在头文件 dsstring.h 中,请实现块链串的子串查找操作:
bool blstr_substr(BLString src, int pos, int len, BLString *sub);
- src为要查找的字符串
pos为子串开始的下标
len为子串的长度
sub在函数调用运行前指向一个已经初始化好的空串,在函数返回时,sub指向串src从第pos个字符起长度为len的子串
函数查找成功返回true,参数不正确返回 false
#include "dsstring.h" // 请不要删除,否则检查不通过
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
BLString* blstr_myinit(BLString* str, int length)
{
if (str == NULL) {
str = (BLString*)malloc(sizeof(BLString));
// assert(str);
}
blstr_init(str);
// 分配空间
str->len = length;
if (length == 0)
return str;
int blk = length / BLOCK_SIZE;
int offset = length % BLOCK_SIZE;
str->head = (Block*)malloc(sizeof(Block));
memset(str->head->ch, BLS_BLANK, sizeof(char) * BLOCK_SIZE);
str->head->next = NULL;
str->tail = str->head;
Block* cnt = str->head;
for (int i = 0; i < blk; i++) {
if (i == blk - 1 && offset == 0)
break;
Block* p = (Block*)malloc(sizeof(Block));
memset(p->ch, BLS_BLANK, sizeof(char) * BLOCK_SIZE);
p->next = NULL;
cnt->next = p;
cnt = cnt->next;
}
str->tail = cnt;
return str;
}
bool blstr_substr(BLString src, int pos, int len, BLString* sub)
{
if (len == 0)
return false;
if (pos > src.len)
return false;
if (len + pos > src.len)
len = src.len - pos;
blstr_myinit(sub, len);
Block *cnt = src.head, *cnt1 = sub->head;
int offset = pos % BLOCK_SIZE, offset1 = 0;
for (int i = 0; i < pos / BLOCK_SIZE; i++)
cnt = cnt->next;
while (len-- && cnt->ch[offset] != BLS_BLANK) {
cnt1->ch[offset1++] = cnt->ch[offset++];
if (offset == BLOCK_SIZE) {
offset = 0;
cnt = cnt->next;
}
if (offset1 == BLOCK_SIZE) {
offset1 = 0;
cnt1 = cnt1->next;
}
}
return true;
}
数组广义表
1.矩阵加法
实现三元组表示的两个稀疏矩阵的加法。相关定义如下:
#define MAXSIZE 100 //假设非零元个数的最大值为100
typedef struct {
int i,j; //非零元的行下标和列下标,i 和 j 从 1 开始计数,与数学中矩阵元素的编号一致
ElemType e; //非零元的值
}Triple;
typedef struct {
Triple data[MAXSIZE]; // 非零元三元组表
int m, n, len; // 矩阵的行数、列数和非零元个数
}TSMatrix;
在三元组中,i 和 j 从 1 开始计数,与数学中矩阵元素的编号一致
矩阵加法函数的原型为:
bool add_matrix(const TSMatrix *pM, const TSMatrix *pN, TSMatrix *pQ);
pM, pN, pQ 分别指向三个矩阵,当 pM 和 pN 两个矩阵不可加时,函数返回 false,否则函数返回 true,且 pQ 指向两个矩阵的和。
#include "tsmatrix.h"
#include <stdio.h>
#include <stdlib.h>
bool add_matrix(const TSMatrix* pM, const TSMatrix* pN, TSMatrix* pQ)
{
int i_M, j_M, i_N, j_N, M, N, Q;
pQ->m = pM->m;
pQ->n = pM->n;
if (pM->m != pN->m || pM->n != pN->n) {
return false;
}
//同时遍历两个三元组,当pM或者pN中其一元素取完循环终止
for (M = 0, N = 0, Q = 0; M < pM->len && N < pN->len;) {
i_M = pM->data[M].i; //M矩阵元素的行号
i_N = pN->data[N].i; //N矩阵元素的行号
j_M = pM->data[M].j; //M矩阵元素的列号
j_N = pN->data[N].j; //N矩阵元素的列号
//因为三元组是按行和列排好序的所以比较行数先判断是否来自同一行
if (i_M > i_N) //N的行号小于M直接将N中元素加入Q矩阵
{
//复制N到Q
pQ->data[Q].i = pN->data[N].i;
pQ->data[Q].j = pN->data[N].j;
pQ->data[Q].e = pN->data[N].e;
N++; //N矩阵地址加一表示向后取一个元素
Q++; //Q矩阵地址加一表示下一元素存放的地址
} else if (i_M < i_N) //B的行号大于A直接将A中元素加入C矩阵
{
//复制M到Q
pQ->data[Q].i = pM->data[M].i;
pQ->data[Q].j = pM->data[M].j;
pQ->data[Q].e = pM->data[M].e;
M++; //M矩阵地址加一表示向后取一个元素
Q++; //Q矩阵地址加一表示下一元素存放的地址
} else //行号相同时
{
//在判断列好号是否来自同一行
if (j_M > j_N) //B的列号小于A直接将B中元素加入C矩阵
{
//复制N到Q
pQ->data[Q].i = pN->data[N].i;
pQ->data[Q].j = pN->data[N].j;
pQ->data[Q].e = pN->data[N].e;
N++; //N矩阵地址加一表示向后取一个元素
Q++; //Q矩阵地址加一表示下一元素存放的地址
} else if (j_M < j_N) //B的列号小于A直接将B中元素加入C矩
{
//复制M到Q
pQ->data[Q].i = pM->data[M].i;
pQ->data[Q].j = pM->data[M].j;
pQ->data[Q].e = pM->data[M].e;
M++; //M矩阵地址加一表示向后取一个元素
Q++; //Q矩阵地址加一表示下一元素存放的地址
} else //相等
{
//判断元素相加是否为零
if ((pM->data[M].e + pN->data[N].e)) //相加不为零
{
pQ->data[Q].i = pM->data[M].i;
pQ->data[Q].j = pM->data[M].j;
pQ->data[Q].e = pM->data[M].e + pN->data[N].e;
Q++;
}
//无论相加是否为零都执行
M++;
N++;
}
}
}
while (M < pM->len) //N取完M未取完
{
//将M中所剩元素依次加入到Q中
pQ->data[Q].i = pM->data[M].i;
pQ->data[Q].j = pM->data[M].j;
pQ->data[Q].e = pM->data[M].e;
M++;
Q++;
}
while (N < pN->len) //M取完N未取完
{
//复制N到Q
pQ->data[Q].i = pN->data[N].i;
pQ->data[Q].j = pN->data[N].j;
pQ->data[Q].e = pN->data[N].e;
N++;
Q++;
}
pQ->len = Q;
return true;
}
2.十字链表
十字链表相关定义如下:
typedef int ElemType;
// 非零元素结点结构
typedef struct OLNode
{
int row,col;
ElemType value;
struct OLNode *right,*down;
}OLNode,*OLink;
// 十字链表结构
typedef struct
{
OLink *rowhead,*colhead;
int rows,cols,nums;
}CrossList, *PCrossList;
1)实现十字链表的初始化操作:
int init_cross_list(PCrossList L, const ElemType *A, int m, int n);
其中 L 指向 CrossList 结构,且各成员已被初始化为0;
A 为 ElemType 类型数组中第一个元素的地址,元素的个数为 m×n 个,按行优先存储(即A[0] 为十字链表第1行第1列的元素;
A[1] 为第1行第2列的元素,A[n] 为第2行第1列的元素,A[n+1] 为第2行第2个元素);
m 表示十字链表的行数,n 表示十字链表的列数。
init_cross_list 函数将 ElemType 数组中非0元素保存到十字链表中,函数返回非 0 元素的个数。
2)实现十字链表的删除操作:
int del_cross_list(PCrossList L, ElemType k);
其中 L 指向 要处理的 CrossList 结构,k 为要删除的元素;
del_cross_list 函数删除十字链表中所有值为 k 的结点,并返回删除结点的个数。
#include "crosslist.h"
#include <stdio.h>
#include <stdlib.h>
int init_cross_list(PCrossList L, const ElemType* A, int m, int n)
{
int i;
OLink q, p;
L->rows = m;
L->cols = n;
L->nums = 0;
if (!(L->rowhead = (OLink*)malloc((m + 1) * sizeof(OLink))))
return 0;
if (!(L->colhead = (OLink*)malloc((n + 1) * sizeof(OLink))))
return 0;
for (i = 0; i <= m; i++)
L->rowhead[i] = NULL;
for (i = 0; i <= n; i++)
L->colhead[i] = NULL;
for (i = 0; i < m * n; i++) {
if (A[i] == 0)
;
else {
if (!(p = (OLNode*)malloc(sizeof(OLNode))))
return 0;
p->col = i%n+1;
p->row = (i+1-p->col)/n+1;
p->value = A[i];
p->right = NULL;
L->nums++;
p->down = NULL;
if (L->rowhead[p->row] == NULL)
L->rowhead[p->row] = p;
else {
q = L->rowhead[p->row];
while (q->right != NULL)
q = q->right;
q->right = p;
}
if (L->colhead[p->col] == NULL)
L->colhead[p->col] = p;
else {
q = L->colhead[p->col];
while (q->down != NULL)
q = q->down;
q->down = p;
}
}
}
return L->nums;
}
int del_cross_list(PCrossList L, ElemType k)
{
int count = 0;
for (int i = 0; i < L->rows+1; ++i) {
OLink p = L->rowhead[i];
while (p) {
if (p->value == k) {
OLink q;
for (q = L->rowhead[i]; q != p && q->right != p; q = q->right) {
}
if (q == p) {
L->rowhead[i] = p->right;
p = L->rowhead[i];
count++;
continue;
} else {
q->right = p->right;
p = p->right;
count++;
continue;
}
}
p = p->right;
}
}
for (int i = 0; i < L->cols+1; ++i) {
OLink p = L->colhead[i];
while (p != NULL) {
if (p->value == k) {
OLink q;
for (q = L->colhead[i]; q != p && q->down != p; q = q->down) {
}
if (q == p) {
L->colhead[i] = p->down;
p = L->colhead[i];
free(q);
continue;
} else {
OLink tmp = q->down;
q->down = p->down;
p = p->down;
free(tmp);
continue;
}
}
p = p->down;
}
}
return count;
}
树二叉树
1.先序遍历
已知二叉树按照二叉链表方式存储,利用栈的基本操作写出先序遍历非递归形式的算法:
void pre_order(BiTree root);
在遍历过程中,pre_order函数需要调用 visit_node 函数来实现对结点的访问,该函数声明如下:
void visit_node(BiTNode *node);
二叉树的相关定义如下:
typedef int DataType;
typedef struct Node{
DataType data;
struct Node* left;
struct Node* right;
}BiTNode, *BiTree;
遍历所使用栈的相关操作如下:
#define Stack_Size 50
typedef BiTNode* ElemType;
typedef struct{
ElemType elem[Stack_Size];
int top;
}Stack;
void init_stack(Stack *S); // 初始化栈
bool push(Stack* S, ElemType x); //x 入栈
bool pop(Stack* S, ElemType *px); //出栈,元素保存到px所指的单元,函数返回true,栈为空时返回 false
bool top(Stack* S, ElemType *px); //获取栈顶元素,将其保存到px所指的单元,函数返回true,栈满时返回 false
bool is_empty(Stack* S); // 栈为空时返回 true,否则返回 false
#include <stdlib.h>
#include <stdio.h>
#include "bitree.h" //请不要删除,否则检查不通过
int *px;
void pre_order(BiTree root)
{
Stack S[Stack_Size];
BiTree T = root;
init_stack(S);
while (T || !is_empty(S))
{
while (T)
{
visit_node(T);
push(S, T);
T = T->left;
}
pop(S, &T);
T = T->right;
}
}
2.路径
假设二叉树采用二叉链表方式存储, root指向根结点,node 指向二叉树中的一个结点,编写函数 path,计算root到 node 之间的路径,(该路径包括root结点和 node 结点)。path 函数声明如下:
bool path(BiTNode* root, BiTNode* node, Stack* s);
其中,root指向二叉树的根结点,node指向二叉树中的另一结点,s 为已经初始化好的栈,该栈用来保存函数所计算的路径,如正确找出路径,则函数返回 true,此时root在栈底,node在栈顶;如未找到,则函数返回 false, 二叉树的相关定义如下:
typedef int DataType;
typedef struct Node{
DataType data;
struct Node* left;
struct Node* right;
}BiTNode, *BiTree;
栈的相关定义及操作如下:
#define Stack_Size 50
typedef BiTNode* ElemType;
typedef struct{
ElemType elem[Stack_Size];
int top;
}Stack;
void init_stack(Stack *S); // 初始化栈
bool push(Stack* S, ElemType x); //x 入栈
bool pop(Stack* S, ElemType *px); //出栈,元素保存到px所指的单元,函数返回true,栈为空时返回 false
bool top(Stack* S, ElemType *px); //获取栈顶元素,将其保存到px所指的单元,函数返回true,栈满时返回 false
bool is_empty(Stack* S); // 栈为空时返回 true,否则返回 false
在提示中,树用缩进的形式展示,如二叉树
,其缩进形式为:。
#include <stdlib.h>
#include <stdio.h>
#include "bitree.h" //请不要删除,否则检查不通过
bool path(BiTNode* root, BiTNode* node, Stack* s)
{
BiTree T = root, p=NULL;
if (T == NULL || node == NULL || !is_empty(s))
return false;
while (T || !is_empty(s)) {
while (T) {
push(s, T);
if (T == node)
return true;
T = T->left;
}
top(s, &T);
if (!T->right || T->right == p) {
p = T;
pop(s, &T);
T = NULL;
} else
T = T->right;
}
return false;
}
3.共同祖先
假设二叉树采用二叉链表方式存储, root指向根结点,p所指结点和q所指结点为二叉树中的两个结点,编写一个计算它们的最近的共同祖先,函数定义如下:
BiTNode * nearest_ancestor(BiTree root, BiTNode *p, BiTNode *q);
其中 root 指向二叉树的根结点,p 和 q 分别指向二叉树中的两个结点。
提示:在完成本题时,可利用 path 函数获取p和q两个结点到根结点之间的路径,之后再计算两条公共路径得出最近的共同祖先。path函数及栈相关定义如下:
bool path(BiTNode* root, BiTNode* node, Stack* s);
#define Stack_Size 50
typedef BiTNode* ElemType;
typedef struct{
ElemType elem[Stack_Size];
int top;
}Stack;
void init_stack(Stack *S); // 初始化栈
bool push(Stack* S, ElemType x); //x 入栈
bool pop(Stack* S, ElemType *px); //出栈,元素保存到px所指的单元,函数返回true,栈为空时返回 false
bool top(Stack* S, ElemType *px); //获取栈顶元素,将其保存到px所指的单元,函数返回true,栈满时返回 false
bool is_empty(Stack* S); // 栈为空时返回 true,否则返回 false
#include <stdlib.h>
#include <stdio.h>
#include "bitree.h" //请不要删除,否则检查不通过
BiTNode* nearest_ancestor(BiTree root, BiTNode* p, BiTNode* q)
{
Stack s1, s2;
BiTNode* ancNode;
init_stack(&s1);
init_stack(&s2);
path(root, p, &s1);
path(root, q, &s2);
if (s1.elem[0] != s2.elem[0])
return NULL;
ancNode = s1.elem[0];
for (int i = 1; i < s1.top && i < s2.top; i++)
{
if (s1.elem[i] != s2.elem[i])
return ancNode;
ancNode = s1.elem[i];
}
return ancNode;
}
4.树转二叉树
使用队列,编写transfrom函数,将普通树转换成对应的二叉树。二叉树的相关定义如下:
typedef int DataType;
typedef struct Node{
DataType data;
struct Node* left;
struct Node* right;
}BiTNode, *BiTree;
普通树节点的定义如下:
#define MAX_CHILDREN_NUM 5
struct _CSNode
{
DataType data;
struct _CSNode *children[MAX_CHILDREN_NUM];
};
typedef struct _CSNode CSNode;
其中,子树的根节点的指针存放在children数组的前k个元素中,即如果children[i]的值为NULL,而children[i-1]不为NULL,则表明该结点只有i棵子树,子树根结点分别保存在children[0]至children[i-1]中。
队列相关定义及操作如下:
struct __Queue
{
int i, j; //指向数组内元素的游标
void **array;
};
typedef struct __Queue Queue;
Queue* create_queue(); //创建队列
bool is_empty_queue(Queue *tree); //队为空返回true,不为空时返回false
void* del_queue(Queue *tree); //结点指针出队
void add_queue(Queue *tree, void *node); //结点指针入队
void free_queue(Queue *tree); //释放队列
transform函数定义如下:
BiTNode* transform(CSNode *root);
其中 root 为普通树的根结点,函数返回该树对应二叉树的根结点。
#include <stdlib.h>
#include <stdio.h>
#include "bitree.h" //请不要删除,否则检查不通过
BiTNode* transform(CSNode* root)
{
if (root == NULL)
return NULL;
//初始化二叉树的根节点
BiTree broot = (BiTree)malloc(sizeof(struct Node));
broot->data = root->data;
broot->left = broot->right = NULL;
//普通树、二叉树初始化、加入队列
Queue* queue = create_queue();
Queue* bqueue = create_queue();
add_queue(queue, root);
add_queue(bqueue, broot);
while (!is_empty_queue(queue))
{
//从普通数和二叉树中分别取出一个结点
CSNode* node = del_queue(queue);
BiTree bTreeNode = del_queue(bqueue);
int i;
BiTree former = NULL;
//遍历普通树结点的所有孩子结点,将孩子加入队列
for (i = 0; i < MAX_CHILDREN_NUM; i++)
{
//孩子非空
if (node->children[i])
{
//二叉树节点初始化并赋值
BiTree bnode = (BiTree)malloc(sizeof(struct Node));
bnode->left = bnode->right = NULL;
bnode->data = node->children[i]->data;
if (i == 0)//普通树的第一个孩子作为二叉树的左孩子
bTreeNode->left = bnode;
else //后面的孩子结点作为前面结点的右孩子
former->right = bnode;
former = bnode;
add_queue(queue, node->children[i]);
add_queue(bqueue, bnode);
}
}
}
free(queue->array);
free(queue);
free(bqueue->array);
free(bqueue);
return broot;
}
图
1.邻接矩阵
试在邻接矩阵存储结构上实现图的基本操作 matrix_insert_vertex 和matrix_insert_arc,相关定义如下:
typedef int VertexType;
typedef enum{
DG, UDG
}GraphType;
typedef struct{
VertexType vertex[MAX_VERTEX_NUM]; //顶点向量
int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵
int vexnum, arcnum; //图的当前顶点数和弧数
GraphType type; //图的种类标志
}MatrixGraph;
int matrix_locate_vertex(MatrixGraph *MG, VertexType vex); //返回顶点 v 在vertex数组中的下标,如果v不存在,返回-1
bool matrix_insert_vertex(MatrixGraph *G, VertexType v);
bool matrix_insert_arc(MatrixGraph *G, VertexType v, VertexType w);
当成功插入顶点或边时,函数返回true,否则(如顶点或边已存在、插入边时顶点v或w不存在)返回false。
#include <stdio.h>
#include "graph.h" // 请不要删除,否则检查不通过
#include <stdio.h>
#include "graph.h" // 请不要删除,否则检查不通过
bool matrix_insert_vertex(MatrixGraph* G, VertexType v)
{
if(G->vexnum==MAX_VERTEX_NUM)
return false;
if (matrix_locate_vertex(G, v) == -1) {
G->vertex[G->vexnum++] = v;
for (int i = 0; i < G->vexnum; i++)//这不是赋权图所以要清空被随机分配的数据
G->arcs[i][G->vexnum - 1] = G->arcs[G->vexnum - 1][i] = 0;
return true;
} else
return false;
}
bool matrix_insert_arc(MatrixGraph* G, VertexType v, VertexType w)
{
int v1 = matrix_locate_vertex(G, v), w1 = matrix_locate_vertex(G, w);
if (v1 == -1 || w1 == -1 || G->arcs[v1][w1] == 1) //假设有向图里v1是弧尾,w1是弧头
return false;
if (G->type == DG) {//加边
G->arcnum++;
G->arcs[v1][w1] = 1;
return true;
} else {
G->arcnum++;
G->arcs[v1][w1] = G->arcs[w1][v1] = 1;
return true;
}
}
2.邻接表1
试在邻接表存储结构上实现图的基本操作 insert_vertex 和 insert_arc,相关定义如下:
typedef int VertexType;
typedef enum{
DG, UDG
}GraphType;
typedef struct ArcNode
{
int adjvex;
InfoPtr *info;
struct ArcNode *nextarc;
}ArcNode;
typedef struct VNode
{
VertexType data;
ArcNode *firstarc;
}VNode;
typedef struct
{
VNode vertex[MAX_VERTEX_NUM];
int vexnum, arcnum;
GraphType type;
}ListGraph;
int locate_vertex(ListGraph* G, VertexType v); //返回顶点 v 在vertex数组中的下标,如果v不存在,返回-1
bool insert_vertex(ListGraph *G, VertexType v);
bool insert_arc(ListGraph *G, VertexType v, VertexType w);
当成功插入顶点或边时,函数返回true,否则(如顶点或边已存在、插入边时顶点v或w不存在)返回false。
#include <stdio.h>
#include "graph.h" //请勿删除,否则检查不通过
bool insert_vertex(ListGraph *G, VertexType v){
int n;
n = locate_vertex(G, v);
if(n == -1){
//if(G->type == DG || G->type == UDG){
G->vertex[G->vexnum].data = v;
G->vertex[G->vexnum].firstarc = NULL;
G->vexnum++;
return true;
}
else
return false;
}
bool insert_arc(ListGraph *G, VertexType v, VertexType w){
int i, j;
ArcNode *p;
i = locate_vertex(G, v);
j = locate_vertex(G, w);
//判结点是否存在,不存在就返回false
if(i == -1|| j == -1)
return false;
//判边是否存在,存在就返回false
//需要注意的是p->adjvex = j这个判断条件,一个是int类型,一个是不要把这个判断条件放到for里面
for(p = G->vertex[i].firstarc; p; p = p->nextarc)
if(p->adjvex == j) return false;
//!!需要注意的是这里是单项插入,不考虑有向无向
//插入到方向就是v-->w
// for(p = G->vertex[j].firstarc; p; p = p->nextarc)
// if(p->adjvex == i) return false;
// if(G->type == UDG){
// p->nextarc = G->vertex[j].firstarc->nextarc;
// p->adjvex = v;
// G->vertex[j].firstarc = p;
// }
//别忘了分配空间,这个是建立一个新的节点
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = j;
G->arcnum++;
if(!G->vertex[i].firstarc)//空的情况
G->vertex[i].firstarc = p;
else{//头插
p->nextarc = G->vertex[i].firstarc->nextarc;
G->vertex[i].firstarc = p;
}
return true;
}
3.邻接表2
试在邻接表存储结构上实现图的基本操作 del_vertex,相关定义如下:
typedef int VertexType;
typedef enum{
DG, UDG
}GraphType;
typedef struct ArcNode{
int adjvex;
InfoPtr *info;
struct ArcNode *nextarc;
}ArcNode;
typedef struct VNode{
VertexType data;
ArcNode *firstarc;
}VNode;
typedef struct{
VNode vertex[MAX_VERTEX_NUM];
int vexnum, arcnum;
GraphType type;
}ListGraph;
int locate_vertex(ListGraph *G, VertexType v); //返回顶点 v 在vertex数组中的下标,如果v不存在,返回-1
bool del_vertex(ListGraph *G, VertexType v); //删除顶点 v
当成功删除顶点或边时,函数返回true,否则(如顶点或边不存在、删除边时顶点v或w不存在)返回false。
#include <stdio.h>
#include "graph.h" //请勿删除,否则检查不通过
bool del_vertex(ListGraph* G, VertexType v)
{
int i;
int V = locate_vertex(G, v);
//检查是否存在该节点
if (V == -1)
return false;
//先删除从该节点出发的边和该节点
while (G->vertex[V].firstarc)
{
ArcNode* P = G->vertex[V].firstarc;
if (P->nextarc)
{ //先free表头结点后面的
ArcNode* temp = P->nextarc;
P->nextarc = temp->nextarc;
free(temp);
}
else {
free(P); //free表头结点
G->vertex[V].firstarc = NULL;
}
G->arcnum--; //边的数量-1
}
G->vexnum--; //结点的数量-1
for (i = V; i < G->vexnum; i++)
{ //表头结点中,后面的向前移动
G->vertex[i] = G->vertex[i + 1];
}
//再删除到该节点的边
for (i = 0; i < G->vexnum; i++)
{
ArcNode *P = G->vertex[i].firstarc, *pNode = NULL;
ArcNode* temp; //存储要被删掉的结点
while (P)
{
if (V == P->adjvex)
{ //P的下个结点是V
if (!pNode)
{ //P是表头结点
temp = G->vertex[i].firstarc;
G->vertex[i].firstarc = P->nextarc;
}
else {
pNode->nextarc = P->nextarc;
temp = P;
}
P = P->nextarc;
free(temp);
G->arcnum--;
}
else {
pNode = P;
P = P->nextarc;
}
}
}
return true;
}
查找
1.哈希表创建
哈希表(Hash Table,也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做哈希函数,存放记录的数组称做哈希表。哈希表相关定义如下:
typedef enum{
HASH_OK,
HASH_ERROR,
HASH_ADDED,
HASH_REPLACED_VALUE,
HASH_ALREADY_ADDED,
HASH_DELETED,
HASH_NOT_FOUND,
} HASH_RESULT;
typedef struct __HashEntry HashEntry;
struct __HashEntry{
union{
char *str_value;
double dbl_value;
int int_value;
} key;
union{
char *str_value;
double dbl_value;
int int_value;
long long_value;
void *ptr_value;
} value;
HashEntry *next;
};
struct __HashTable{
HashEntry **bucket;
int size;
HASH_RESULT last_error;
};
typedef struct __HashTable HashTable;
// 创建大小为hash_size的哈希表,创建成功后返回HashTable类型的指针,否则返回NULL。
HashTable *create_hash(int hash_size);
哈希表相关说明:
- HASH_RESULT 类型为相关函数的返回类型
- HashEntry 为哈希表所保存元素(即键值对 《key, value》)类型
- HashTable 为哈希表,其中 bucket 指向大小为size的、元素类型为 HashEntry*的指针数组
- 哈希表采用链地址法处理冲突
请实现 create_hash 函数,创建指定大小的哈希表。
#include <stdio.h>
#include <stdlib.h>
#include "hash.h"
#include <string.h>
HashTable* create_hash(int size)
{
HashTable* H;
H = (HashTable*)malloc(1 * sizeof(HashTable));
if (H == NULL)
return NULL;
H->bucket = (HashEntry**)malloc(size * sizeof(HashEntry*));
if (H->bucket == NULL) {
free(H);
return NULL;
}
memset(H->bucket, 0, size * sizeof(HashEntry*));
H->size = size;
H->last_error = HASH_OK;
return H;
}
2.哈希表添加
哈希表(Hash Table,也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做哈希函数,存放记录的数组称做哈希表。哈希表相关定义如下:
typedef enum{
HASH_OK,
HASH_ERROR,
HASH_ADDED,
HASH_REPLACED_VALUE,
HASH_ALREADY_ADDED,
HASH_DELETED,
HASH_NOT_FOUND,
} HASH_RESULT;
typedef struct __HashEntry HashEntry;
struct __HashEntry{
union{
char *str_value;
double dbl_value;
int int_value;
} key;
union{
char *str_value;
double dbl_value;
int int_value;
long long_value;
void *ptr_value;
} value;
HashEntry *next;
};
struct __HashTable{
HashEntry **bucket;
int size;
HASH_RESULT last_error;
};
typedef struct __HashTable HashTable;
// 向哈希表中添加元素,其中键类型为char*, 元素类型为int。
HASH_RESULT hash_add_int(HashTable * table, const char * key, int value);
哈希表相关说明:
- HASH_RESULT 类型为相关函数的返回类型
- HashEntry 为哈希表所保存元素(即键值对 《key, value》)类型
- HashTable 为哈希表,其中 bucket 指向大小为size的、元素类型为 HashEntry*的指针数组
- 哈希表采用链地址法处理冲突
请实现 hash_add_int 函数,向哈希表中添加元素,其中键类型为char*, 元素类型为int。在添加过程中,如果要添加的键值key已在哈希表中,且对应的值value也已存在,则函数返回 HASH_ALREADY_ADDED;如果要添加的键值key已在哈希表中,但对应的值value不同,则函数将value值更新到哈希表中,之后返回 HASH_REPLACED_VALUE;如果要添加的键值key不在哈希表中,则函数创建 HashEntry 类型,并将其加入到哈希表中,且函数返回 HASH_ADDED。本题所用的哈希函数如下:
long hash_string(const char *str)
{
long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
if(hash < 0)
hash *= -1;
return hash;
}
#include <stdio.h>
#include "stdlib.h"
#include "hash.h"
#include <string.h>
int Find(HashTable* table, const char* key, int value, long keyhash)
{
HashEntry* Head = table->bucket[keyhash];
while (Head && strcmp(Head->key.str_value, key)) {
Head = Head->next;
}
if (!Head)
return -1;
else if (Head->value.int_value == value)
return 1;
else {
Head->value.int_value = value;
return 0;
}
}
HASH_RESULT hash_add_int(HashTable* table, const char* key, int value)
{
int p;
long keyhash = hash_string(key) % table->size;
p = Find(table, key, value, keyhash);
if (p == -1) {
HashEntry* Node = (HashEntry*)malloc(sizeof(HashEntry));
if (!Node)
return HASH_ERROR;
Node->key.str_value = (char*)malloc(100);
if (Node->key.str_value == NULL) {
return HASH_ERROR;
}
Node->key.str_value = (char*)key;
Node->value.int_value = value;
//Node->next = table->bucket[keyhash]; //?
table->bucket[keyhash] = Node;
return HASH_ADDED;
} else if (p == 0)
return HASH_REPLACED_VALUE;
else
return HASH_ALREADY_ADDED;
}
3.AVL添加
平衡二叉树,是一种二叉排序树,其中每个结点的左子树和右子树的高度差至多等于1。它是一种高度平衡的二叉排序树。现二叉平衡树结点定义如下:
typedef struct node
{
int val;
struct node *left;
struct node *right;
struct node *parent;
int height;
} node_t;
请实现平衡二叉树的插入算法:
//向根为 root 的平衡二叉树插入新元素 val,成功后返回新平衡二叉树根结点
node_t *avl_insert(node_t *root, int val);
#include "avl.h"
#include <stdio.h>
#include <stdlib.h>
void update(node_t* root)
{
if (root == NULL)
return;
int h = 1;
if (root->left) {
int lh = root->left->height + 1;
h = lh > h ? lh : h;
root->left->parent = root;
}
if (root->right) {
int rh = root->right->height + 1;
h = rh > h ? rh : h;
root->right->parent = root;
}
root->height = h;
}
void LL(node_t** t)
{
node_t* tmp = (*t)->left;
(*t)->left = tmp->right;
tmp->right = (*t);
(*t) = tmp;
update((*t)->right->left);
update((*t)->right);
update((*t));
}
void RR(node_t** t)
{
node_t* tmp = (*t)->right;
(*t)->right = tmp->left;
tmp->left = (*t);
(*t) = tmp;
update((*t)->left->right);
update((*t)->left);
update((*t));
}
void LR(node_t** t)
{
RR(&(*t)->left);
LL(t);
}
void RL(node_t** t)
{
LL(&(*t)->right);
RR(t);
}
node_t* avl_insert(node_t* root, int val)
{
if (root == NULL) {
node_t* rt = (node_t*)malloc(sizeof(node_t));
rt->left = rt->right = rt->parent = NULL;
rt->height = 1;
rt->val = val;
return rt;
}
if (val <= root->val) {
root->left = avl_insert(root->left, val);
int left_h = root->left->height;
int right_h = root->right ? root->right->height : 0;
if (left_h - right_h > 1) {
if (val <= root->left->val) {
LL(&root);
} else {
LR(&root);
}
}
} else {
root->right = avl_insert(root->right, val);
int left_h = root->left ? root->left->height : 0;
int right_h = root->right->height;
if (right_h - left_h > 1) {
if (val > root->right->val) {
RR(&root);
} else {
RL(&root);
}
}
}
update(root);
return root;
}
排序
1.堆辅助函数
二叉堆是完全二叉树或者是近似完全二叉树。二叉堆有两种:最大堆和最小堆。
- 最大堆(大顶堆):父结点的键值总是大于或等于任何一个子节点的键值,即最大的元素在顶端;
- 最小堆(小顶堆):父结点的键值总是小于或等于任何一个子节点的键值,即最小的元素在顶端。
- 二叉堆子结点的大小与其左右位置无关。
二叉堆一般用数组来表示。例如,根节点在数组中的位置是0,第n个位置的子节点分别在2n+1和 2n+2。 因此,第0个位置的子节点在1和2,1的子节点在3和4。以此类推。这种存储方式便于寻找父节点和子节点。在二叉堆上可以进行插入节点、删除节点、取出值最小的节点、减小节点的值等基本操作。
“最小堆”的定义如下:
typedef struct _otherInfo
{
int i;
int j;
}OtherInfo;
typedef struct _minHeapNode
{
int value;
OtherInfo otherInfo;
}MinHeapNode, *PMinHeapNode;
typedef struct _minPQ {
PMinHeapNode heap_array; // 指向堆元素数组
int heap_size; // 当前堆中的元素个数
int capacity; //堆数组的大小
}MinHeap, *PMinHeap;
请实现最小堆的四个辅助函数:
int parent(int i); //返回堆元素数组下标为 i 的结点的父结点下标
int left(int i); //返回堆元素数组下标为 i 的结点的左子结点下标
int right(int i); //返回堆元素数组下标为 i 的结点的右子结点下标
void swap_node(MinHeapNode *x, MinHeapNode *y); //交换两个堆元素的值
#include <stdio.h>
#include <stdlib.h>
#include "minbinheap.h" // 请不要删除,否则检查不通过
//返回堆元素的父节点下标
//n->2n+1 2n+2
int parent(int i) {
return (i-1) / 2;
}
//返回左子节点
int left(int i){
return 2 * i + 1;
}
int right(int j) {
return 2 * j + 2;
}
//交换两个堆元素的值
void swap_node(MinHeapNode* x, MinHeapNode* y) {
int value;
int i, j;
value = y->value;
i = y->otherInfo.i;
j = y->otherInfo.j;
y->value = x->value;
y->otherInfo.i = x->otherInfo.i;
y->otherInfo.j = x->otherInfo.j;
x->value = value;
x->otherInfo.i = i;
x->otherInfo.j = j;
}
2.堆初始化
二叉堆一般用数组来表示。例如,根节点在数组中的位置是0,第n个位置的子节点分别在2n+1和 2n+2。 因此,第0个位置的子节点在1和2,1的子节点在3和4。以此类推。这种存储方式便于寻找父节点和子节点。在二叉堆上可以进行插入节点、删除节点、取出值最小的节点、减小节点的值等基本操作。
“最小堆”的定义如下:
typedef struct _otherInfo
{
int i;
int j;
}OtherInfo;
typedef struct _minHeapNode
{
int value;
OtherInfo otherInfo;
}MinHeapNode, *PMinHeapNode;
typedef struct _minPQ {
PMinHeapNode heap_array; // 指向堆元素数组
int heap_size; // 当前堆中的元素个数
int capacity; //堆数组的大小
}MinHeap, *PMinHeap;
请实现最小堆的初始化函数:
void init_min_heap(PMinHeap pq, int capacity);
其中 pq指向堆,capacity为堆元素数组的初始化大小。
#include <stdio.h>
#include <stdlib.h>
#include "minbinheap.h"
//pq指向堆,capacity为堆元素数组的初始化大小
void init_min_heap(PMinHeap pq, int capacity){
pq->capacity = capacity;
pq->heap_size = 0;
pq->heap_array = (PMinHeapNode)malloc(sizeof(MinHeapNode) * pq->capacity);
return;
}
3.堆元素插入
二叉堆一般用数组来表示。例如,根节点在数组中的位置是0,第n个位置的子节点分别在2n+1和 2n+2。 因此,第0个位置的子节点在1和2,1的子节点在3和4。以此类推。这种存储方式便于寻找父节点和子节点。在二叉堆上可以进行插入节点、删除节点、取出值最小的节点、减小节点的值等基本操作。
“最小堆”的定义如下:
typedef struct _otherInfo
{
int i;
int j;
}OtherInfo;
typedef struct _minHeapNode
{
int value;
OtherInfo otherInfo;
}MinHeapNode, *PMinHeapNode;
typedef struct _minPQ {
PMinHeapNode heap_array; // 指向堆元素数组
int heap_size; // 当前堆中的元素个数
int capacity; //堆数组的大小
}MinHeap, *PMinHeap;
请实现最小堆的元素插入函数:
bool heap_insert_value(PMinHeap pq, int value);
其中 pq指向堆,value 为要插入的堆元素。
(注:假设辅助函数 parent 和 swap_node 已正确实现,heap_insert_value 函数可直接使用。)
#include <stdio.h>
#include <stdlib.h>
#include "minbinheap.h"
//parent 和 swap_node 已正确实现,pq指向堆,value 为要插入的堆元素
bool heap_insert_value(PMinHeap pq, int value){
//如果满了,就无法插入
if (pq->heap_size == pq->capacity)return false;
int i = pq->heap_size;
int j;
int temp;
//p指向根节点
MinHeapNode* p = pq->heap_array;
//插入最后一位
p[i].value = value;
pq->heap_size++;
while (1) {
j = parent(i);
//j不是节点的父亲
if (p[i].value <= p[j].value) {
temp = p[i].value;
p[i].value = p[j].value;
p[j].value = temp;
i = j;
if (i == 0)return true;
}
else return true;
}
}
4.堆化
二叉堆一般用数组来表示。例如,根节点在数组中的位置是0,第n个位置的子节点分别在2n+1和 2n+2。 因此,第0个位置的子节点在1和2,1的子节点在3和4。以此类推。这种存储方式便于寻找父节点和子节点。在二叉堆上可以进行插入节点、删除节点、取出值最小的节点、减小节点的值等基本操作。
“最小堆”的定义如下:
typedef struct _otherInfo
{
int i;
int j;
}OtherInfo;
typedef struct _minHeapNode
{
int value;
OtherInfo otherInfo;
}MinHeapNode, *PMinHeapNode;
typedef struct _minPQ {
PMinHeapNode heap_array; // 指向堆元素数组
int heap_size; // 当前堆中的元素个数
int capacity; //堆数组的大小
}MinHeap, *PMinHeap;
请实现最小堆的“堆化”函数:
void min_heapify(PMinHeap pq, int i);
其中 pq指向堆,i 为堆元素在数组中的下标。该函数假设元素i对应的子树都已经是最小堆(符合最小堆的要求),但元素i为根的子树并不是最小堆,min_heapify将对元素i及其子树的各结点进行调整,使其为一个最小堆。
(注:假设辅助函数 left、right、parent 和 swap_node 已正确实现,min_heapify 函数可直接使用。)
#include <stdio.h>
#include <stdlib.h>
#include "minbinheap.h"
void min_heapify(PMinHeap pq, int i){
int j = 2 * i + 1;
MinHeapNode* p = pq->heap_array;
while (j <= pq->heap_size - 1){
//j为最小值的堆
if (p[j + 1].value < p[j].value)
j = j + 1;
if (p[j].value < p[i].value) {
swap_node(&p[j], &p[i]);
i = j;
j = 2 * i + 1;
}
else return;
}
}
5.数组合并
假设有 n 个长度为 k 的已排好序(升序)的数组,请设计数据结构和算法,将这 n 个数组合并到一个数组,且各元素按升序排列。即实现函数:
void merge_arrays(const int* arr, int n, int k, int* output);
其中 arr 为按行优先保存的 n 个长度都为 k 的数组,output 为合并后的按升序排列的数组,大小为 n×k。
时间要求(评分规则),当 n > k 时:
- 满分:时间复杂度不超过 O(n×k×log(n))
- 75分:时间复杂度不超过 O(n×k×log(n)×k)
- 59分:其它,如:时间复杂度为 O(n2×k2) 时。
#include<stdio.h>
#include<stdlib.h>
void max_heapify(int* p, int i, int size) {
int j = 2 * i + 1, t=p[i];
while (j <= size - 1) {
if (j + 1 <= size - 1 && p[j] < p[j + 1])j = j + 1;
if (p[j] > t) {
p[i] = p[j];
i = j;
j = 2 * i + 1;
}
else break;
}
p[i] = t;
}//假设i之后都是大根堆,调整i使从i开始都是大根堆
void merge_arrays(const int* arr, int n,int k,int* output) {
int size=n*k;
int x, i, *array;
array = (int*)malloc(size * sizeof(int));
for (i = 0; i <= size - 1; i++) {
array[i] = arr[i];
}
for (i=size/2-1; i >=0 ; i--) {
max_heapify(array, i, size);
}//将整个堆大根堆化
for (i = size-1; i >= 1; --i) {
x = array[0];
array[0] = array[i];
array[i] = x;
max_heapify(array, 0, i);
}
for (i = 0; i <= size - 1; i++) {
output[i] = array[i];
}
}