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];
}

icoding复习7, 8相关推荐

  1. icoding复习5 树 感觉难度巨大....

    icoding 复习5 1. 先序遍历  已知二叉树按照二叉链表方式存储,利用栈的基本操作写出先序遍历非递归形式的算法: void pre_order(BiTree root); 二叉树的相关定义如下 ...

  2. icoding复习4 数组 十字链表

    icoding 复习4 1. 矩阵加法 实现三元组表示的两个稀疏矩阵的加法. #define MAXSIZE 100          //假设非零元个数的最大值为100 typedef struct ...

  3. icoding复习3

    icoding复习3 1. 不调用库函数,自己实现字符串的比较操作:该操作当比较的两个字符是都是字母,且两个字符互为大小写 (如a和A.e和E)时认为两个字符相同,否则不同,其比较结果按这两个字符的原 ...

  4. icoding复习6 图

    icoding复习6 1. 邻接表1 试在邻接表存储结构上实现图的基本操作 insert_vertex 和 insert_arc,相关定义如下: typedef int VertexType; typ ...

  5. icoding复习1,2

    icoding复习 1 链表 倒数查找 1. 已知一个带有表头结点的单链表, 假设链表只给出了头指针L.在不改变链表的前提下,请设计一个尽可能高效的算法, 查找链表中倒数第k个位置上的结点(k为正整数 ...

  6. 已知小红今年12岁c语言编程,C语言程序设计第轮复习习题.doc

    C语言程序设计第轮复习习题 第1章 C语言概述.以下叙述正确的是 . A.在C程序中,main函数必须位于子程序的最前面 B.C程序的每一行中只能写一条语句 C.在对一个C程序进行编译的过程中,可发现 ...

  7. 可微偏导数一定存在_数学分析复习——偏导数(1)

    前言:微积分开始就是死刷题,背定义.然后我发现自己遗忘的速度简直怀疑人生.特别是在学物理以后,发现微积分根本就没有理解.一上来基础就没打好.所以希望能够慢慢地把数学分析,线性代数,偏微分,实变补起来 ...

  8. 考研计算机专业课怎么复习,2016考研计算机专业课如何复习?

    2016考研计算机专业课如何复习? ?基础复习阶段 以指定参考书为主,兼顾笔记,进行专业课的第一轮复习.看书要以理解为主,不必纠缠于细节,并在不懂的知识点处做上标记. 第一步,选择一本难度适宜.内容全 ...

  9. 计算机应用 含升学方向,对口升学《计算机应用基础》复习资料总汇(含答案))讲述.doc...

    对口升学<计算机应用基础>复习资料总汇 第一部分?? 一.单项选择题 1.世界上第一台电子数字计算机取名为(????). A.UNIVAC????B.EDSAC????C.ENIAC??? ...

最新文章

  1. 一台服务器两个mysql_在一台服务器上安装两个或多个mysql的实现步骤_MySQL
  2. iOS 设置UILabel 的内边距
  3. 多媒体技术PI 第三期—网络传输线上圆桌
  4. 小程序 封装table组件
  5. LeetCode 254. 因子的组合(回溯)*
  6. 【BZOJ】【1036】树的统计
  7. linux系统清空文件内容
  8. @Transactional注解下,Mybatis循环取序列的值,但得到的值都相同的问题
  9. php soap header_PHP调用有SoapHeader认证的WebService实例
  10. macOS 内置的端口扫描工具
  11. 合宙Air720U724U722U Lua 固件更新说明
  12. python tkinter ttk的使用(上)
  13. 解除Windows XP IIS 10个并发连接数的限制
  14. sketch八款设计插件,画图效率翻倍
  15. 迭代回顾会议咨询记录
  16. WaveSwipeRefreshLayout实现微信热文精选,微信热文安卓app版,activity中viewpager套fragment
  17. 玩乐|杭州夏季纳凉好去处
  18. 在自建虚拟环境中出现的问题
  19. 右手螺旋判断磁感应强度方向_高中物理电流磁场的判断方法是什么?叉乘方向右手螺旋定则如何运用...
  20. index.dat结构分析

热门文章

  1. 浅谈C++类(1)--概念和构造函数
  2. 【看动漫学编程】程序员在异世界生个娃 第2篇:外挂已准备就绪
  3. 《零基础看得懂的C++入门教程 》——(4)条件判断原来如此
  4. 【二】Windows API 零门槛编程指南——CreateWindow 窗口创建 “万字长篇专业术语全解”
  5. 可见光能量范围_JACS:游书力团队通过可见光促进的吲哚衍生物分子内去芳构化合成环丁烷稠合的四环吲哚螺环...
  6. 华为手机Android系统优缺点,第一次安卓机优缺点总结(对比iOS系统)
  7. Oracle DBA
  8. 寒窗苦读十多年,我的毕业论文只研究了一个「屁」
  9. 三年租男友回家竟花了10万......
  10. 区块链、自动驾驶、人工智能鏖战开始 谁将成为下一个风口?