icoding复习7 哈希,AVL 查找
必考点!!!
1. 哈希表创建
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
#include
#include "hash.h"
HashTable* create_hash(int size){
HashTable *table;
if(!(table = (HashTable *)malloc(sizeof(HashTable)))) return false;
if(!(table->bucket = (HashTable *)malloc(size * sizeof(HashEntry *)))){
free(table); return false;
}
table->size = size;
table->last_error = HASH_OK;
return table;
}
必考!!!
2. 哈希表添加
// 向哈希表中添加元素,其中键类型为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){
...
}
HASH_RESULT hash_add_int(HashTable *table, const char *key, int value){
int i = hash_string(key) % table->size;
HashEntry *p;
p = table->bucket[i];
if(!p){//该关键字不存在
p = (HashEntry *)malloc(sizeof(HashEntry)); // 判空略
strcpy(p->key.str_value, key);
p->value.int_value = value;
table->bucket[i] = p;
return HASH_ADDED;
}
//关键字存在,先判断关键字是否相等,再判断值;如果关键字不等那么最后还要链地址添加
while(p){
if(strcmp(p->key.str_value, key)){
if(p->value.int_value == value){
return HASH_ALREADY_ADDED;
}
else{
p->value.int_value = value;
return HASH_REPLACED_VALUE;
}
}
else
p = p->next;
}
HashEntry *q = (HashEntry *)malloc(sizeof(HashEntry)); // 判空略
q->key = (char *)malloc(sizeof(char) *strlen(key));//判空类似前面的
strcpy(q->key.str_value, key);
q->value.int_value = value;
p->next = q;
q->next = NULL;
return HASH_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
#include
#include "avl.h"
int get_height(node_t *p){
if(!p)
return 0;
else
return p->height;
}
node_t* avl_insert(node_t *root, int val){
//首先清晰字母定义;
//val新插入结点元素值,height高度!!!
//定义查找过程中出现的距离插入结点最近的平衡因子不为零的结点A
//定义A的孩子结点为B,需要旋转的结点
//定义插入节点为s,s的值为val
//平衡因子:左子树减去右子树深度
node_t *s, *A, *B, *C, *p, *fp;
//依次:插入点, 失衡点(也可能是旋转点),旋转点,旋转点(也可能是插入点=s),动点,跟踪点
int i, k;//平衡因子
s = (node_t *)malloc(sizeof(node_t));
if(!s) return NULL;
s->val = val;
s->left = s->parent = s->right = NULL;
s->height = 1;
//类似于指针跟踪技术,p为动指针,A为跟踪指针
A = root; A->parent = NULL;
p = root; p->parent = NULL;
//找出A
if(!p)
root = s;
else{
while(p){
//先找出最近的A->height!=0的结点, 就是最后的失衡点
i = get_height(p->left) - get_height(p->right);
if(i){
A = p;
A->parent = p->parent;
}
//fp跟踪p,因为下一步p会往下移动,p最终指向s的那一层
fp = p;
if(val < p->val)
p = p->left;
else
p = p->right;
}//p最终指向NULL就退出循环
}
//插入, 此时fp是p的前一个结点,p指向空
if(val < fp->val)
fp->left = s;
else
fp->right = s;
//确定旋转结点B,修改A的平衡因子
if(val < A->val)
B = A->left;
else
B = A->right;
A->height++;
//修改路径B到s的高度, B在A的下一层
p = B; // p最终指向s, 之前指向的是s这一层,但是是空
while(p != s){
p->height++;
if(val < p->val)
p = p->left;
else
p = p->right;
}
//最终s的高度没有++的 , 初始值赋为1
//调整完高度就修改结点和指针, 首先需要判断失衡类型
//分为LL,LR,RR,RL
//结点A,B平衡因子在修改指针的过程中会变化,但是路径上的结点不会
//指针修改包括s结点指针和原来双亲结点指针
i = get_height(A->left) - get_height(A->right);
k = get_height(B->left) - get_height(B->right);
if(i == 2 && k == 1){//LL
//B作为旋转结点
//先改结点指针, 此时s插入在B的左子树下, 原来可以认为B左右子树,A右子树均为空
A->left = B->right;
B->right = A;
//考虑原来A结点的指针,改成B后相应的指针也要改变,下面同理
if(A->parent == NULL)
root = B;
else if(A->parent->left == A)
A->parent->left = B;
else
A->parent->right = B;
}
else if(i == -2 && k == -1){//RR
A->right = B->left;
B->left = A;
if(A->parent == NULL)
root = B;
else if(A->parent->left == A)
A->parent->left = B;
else
A->parent->right = B;
}
else if(i == 2 && k == -1){//LR
//此时认为C的左右子树空,B逆时针旋转,A顺时针旋转, s插入C的左子树或者右子树
//如果C结点也是空,也就是说B右子树空,那么直接插入C=s为B右子树,此时A右子树也是空的
C = B->right;
B->right = C->left;
A->left = C->right;
C->left = B;
C->right = A;
if(A->parent == NULL)
root = C;
else if(A->parent->left == A)
A->parent->left = C;
else
A->parent->right = C;
}
else if(i == -2 && k == 1){//RL
//和LR一样,画图来看就好
C = B->left;
A->right = C->left;
B->left = C->right;
C->left = A;
C->right = B;
if(A->parent == NULL)
root = C;
else if(A->parent->left == A)
A->parent->left = C;
else
A->parent->right = C;
}
return root;
}
icoding复习8 堆排
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); //交换两个堆元素的值
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
重点区别
1. PMinHeapNode heap_array
2. MinHeapNode *heap_array
3. MinHeapNode heap_array[capacity]//三者等价,前两者没分配空间
对于heap_array[i].value,记得点运算符, 因为这个不是指针了!!!!!
#include
#include
#include "minbinheap.h" // 请不要删除,否则检查不通过
int parent(int i){
return (i - 1) / 2;
}
int left(int i){
return 2*i+1;
}
int right(int i){
return 2*i+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;
return;
}
2. 堆初始化
请实现最小堆的初始化函数:
void init_min_heap(PMinHeap pq, int capacity);
其中 pq指向堆,capacity为堆元素数组的初始化大小。
void init_min_heap(PMinHeap pq, int capacity){//小根堆, 小的在上面
//题目没有明确
// pq = (PMinHeap)malloc(sizeof(MinHeap));
// if(!pq) return;
if(!(pq->heap_array = (PMinHeapNode)malloc(sizeof(MinHeapNode) * capacity))){
free(pq); return;
}
pq->capacity = capacity;
pq->heap_size = 0;
//return;
}
3. 堆化
请实现最小堆的“堆化”函数:
void min_heapify(PMinHeap pq, int i);
其中 pq指向堆,i 为堆元素在数组中的下标。该函数假设元素i对应的子树都已经是最小堆
(符合最小堆的要求),但元素i为根的子树并不是最小堆,
min_heapify将对元素i及其子树的各结点进行调整,使其为一个最小堆。
void min_heapify(PMinHeap pq, int i){
int j = parent(i);
//if(pq->heap_array[i].value > pq->heap_array[j].value) return;
for(; j >= 0 && pq->heap_array[i].value > pq->heap_array[j].value; i = j, j = parent(i))
swap_node(&pq->heap_array[i], &(pq->heap_array[j]));
}
void min_heapify(PMinHeap pq, int i){//一行
for(int j = parent(i); j >= 0 && pq->heap_array[i].value > pq->heap_array[j].value; i = j, j = parent(i))swap_node(&(pq->heap_array[i]), &(pq->heap_array[j]));}
4. 堆元素插入
请实现最小堆的元素插入函数:
bool heap_insert_value(PMinHeap pq, int value);
其中 pq指向堆,value 为要插入的堆元素。
bool heap_insert_value(PMinHeap pq, int value){//小根堆,子树关键字大于等于根的关键字
//if(pq->heap_size == pq->capacity - 1) return false;
int i, j;
i = pq->heap_size;
j = parent(i);
pq->heap_array[i].value = value;
while(i){
if(value < pq->heap_array[j].value){
swap_node(&(pq->heap_array[i]), &(pq->heap_array[j]));
i = j;
j = parent(i);
}
else{
pq->heap_size++;
return true;
}
}
}
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
#include
//建立大根堆(大的在上面)
void build_bigroot(int *a, int i, int size){
//参数说明:a传入的数组,i待排序元素开始位置,size数组元素个数(长度)
int j, s;
//j是i的孩子指针,s暂存排序的元素
//不设监视哨,所以从'0'(i)开始排序,孩子为2*i+1
j = 2 * i + 1;
s = a[i];
while(j < size){//可以设置bool变量测试是否筛选完毕,减小时间复杂度
//不可以取等哈 ,0开始的
//存在右子树并且右子树更大,那么筛选右子树
if(j + 1 < size && a[j+1] > a[j])
j++;
if(s >= a[j])
break;
else{//如果大的记录在下,那么上浮
a[i] = a[j];
i = j;
j = 2 * i + 1;
}
}
//最后把筛选完成后的数据放在合适位置
a[i] = s;
}
void merge_arrays(const int *arr, int n, int k, int* output){
//说明一下arr为const int类型,不能改动,所以只好浪费时空复制出来在变动
int i, size, x;
size = n * k;
int a[size];
//大根堆是大的在上,没有什么其他要求.堆排后大的放大到最后,形成升序排列的小根堆
for(i= 0; i < size; i++)
a[i] = arr[i];
//建立大根堆
for(i = size / 2 -1; i >= 0; i--)
build_bigroot(a, i , size);
//堆排
for(i = size - 1; i >=1; i--){
x = a[0];
a[0] = a[i];
a[i] = x;
build_bigroot(a, 0, i);
}
for(i = 0; i < size; i++)
output[i] = a[i];
}