上次写的博客总结了数组实现哈希表,这次总结一下链表实现哈希表。
哈希表的基础知识在上次的数组实现哈希表里讲过了,这是链接:数组哈希
跳表结构如下:
那么我们来写它需要的结构体以及所需函数:先是数据类型的结构体:
struct datatype {
int first;
char second[20];
};
这里跟数组哈希是一样的,一个是需要取余的键值,一个是存放的数据
既然是链表结构,那么该有的链表节点我们得写一下,这个看我的数据结构专栏的帅哥都很熟悉,或者经常写链表的靓仔都很熟悉,所以直接上了:
struct node {
struct datatype* data;
struct node* next;
};
struct node* createnode(struct datatype* data) {
struct node* newnode = (struct node*)malloc(sizeof(struct node));
newnode->data = data;
newnode->next = NULL;
return newnode;
}
那么我们刚才看了跳表的基本结构,我们抽象出来跳表的基本单元也就是第一列节点的情况:
然后我们把基础单元封装成结构体,并写出它的创建的函数:
struct skiplistnode {
struct datatype* data;
struct node* firstnode;
struct skiplistnode* next;
};
struct skiplistnode* createskiplistnode(struct datatype* data) {
struct skiplistnode* newnode = (struct skiplistnode*)malloc(sizeof(struct skiplistnode));
newnode->data = data;
newnode->firstnode = NULL;//横向的点
newnode->next = NULL;//竖向的节点
return newnode;
}
接下来是抽象一下跳表结构,也就是若干个跳表节点和自己的大小:
struct skiplist {
struct skiplistnode* headnode;
int sizeskip;
int div;//取余法
};
//创建一个跳表:
struct skiplist* createskiplist(int div) {
struct skiplist* list = (struct skiplist*)malloc(sizeof(struct skiplist));
list->headnode = NULL;
list->sizeskip = 0;
list->div = div;
return list;
}
好了跳表结构我们完成了初始化的工作,接下来是在表中记录元素的函数,这里运用到了有序链表,和有序插入的知识,逻辑上稍微有点复杂,我在注释上标清楚我每一步在干嘛,有问题的也可以在评论提问我,大家注意画图理解:
//这个函数也就是找到合适的位置插入
void insertnodebysort(struct skiplist* list, struct datatype* data) {//注意我所有的data传入的都是一级指针,这样占用空间少也运行速度快
int datahashpos = data->first % list->div;//取余法第一步
struct skiplistnode* newskipnode = createskiplistnode(data);//创建个跳表节点
if (list->headnode == NULL) {//这里是表中无元素,直接弄成表头节点就行了
list->headnode = newskipnode;
list->sizeskip++;
}
else {//表中有元素的情况
struct skiplistnode* pmove = list->headnode;
struct skiplistnode* pmovefront = NULL;//有序链表的插入,运用两个指针查找位置
if (pmove->data->first % list->div > datahashpos) {//直接比它大,说明能直接插入,也就是新插入元素作为表头
//插入的节点当表头
newskipnode->next = list->headnode;
list->headnode = newskipnode;
list->sizeskip++;
}
else {//其他情况,也就是需要往后找
while ((pmove->data->first) % (list->div) < datahashpos) {
pmovefront = pmove;
pmove = pmovefront->next;
//到达表位处理,也就是找到位置
if (pmove == NULL) {
break;
}
}
//新节点的插入考虑hash冲突,以及关键词相同的时候的处理
if (pmove != NULL && (pmove->data->first % list->div) == datahashpos) {//遍历表,找不到位置
//如果存在位置,讨论。键相同,采用覆盖的方式
if (pmove->data->first == data->first) {//键相同
strcpy(pmove->data->second, data->second);
}
else {//不相同,冲突处理
struct node* newnode = createnode(data);
struct node* ppmove = pmove->firstnode;
if (ppmove == NULL) {
newnode->next = pmove->firstnode;
pmove->firstnode = newnode;
list->sizeskip++;
}
else {
while (ppmove->data->first != data->first) {
ppmove = ppmove->next;
if (ppmove == NULL) {
break;
}
}
if (ppmove == NULL) {
newnode->next = pmove->firstnode;
pmove->firstnode = newnode;
list->sizeskip++;
}
else {
strcpy(ppmove->data->second, data->second);
}
}
}
}
else {//考虑不冲突的情况
pmovefront->next = newskipnode;
newskipnode->next = pmove;
list->sizeskip++;
}
}
}
}
好了,插入元素,我们完成了,那么这个数据结构其实已经完成了,接下来是验证我们做的对不对:
一个打印函数:
竖向打印,横向有冲突的在后面插入的,要做判断,如果有横向走到NULL结束,打印
void printlist(struct skiplist* list) {
struct skiplistnode* pmove = list->headnode;//竖向打印的指针
while (pmove) {
printf("(%d,%s)\t", pmove->data->first, pmove->data->second);//竖向的打印
struct node* ppmove = pmove->firstnode;//检测横向的指针
while (ppmove) {
printf("(%d,%s)\t", ppmove->data->first, ppmove->data->second);//横向的打印
ppmove = ppmove->next;
}
printf("\n");//每次横向打印完毕转行一下,方便我们看结果
pmove = pmove->next;//记得移动指针
}
printf("\n");
}
主函数:
int main() {
struct skiplist* list = createskiplist(10);
struct datatype array[7] = {
1,"love",4,"my",11,"baby",8,"hate",9,"unhappy",6,"aaa",7,"原谅我"
};
for (int i = 0; i < 7; i++) {
insertnodebysort(list, &array[i]);//其中检验了,空表情况,以及正常,还有冲突现象
}
printlist(list);
struct datatype a = { 20,"yyx" };//检验直接表头的情况
insertnodebysort(list, &a);
printlist(list);
return 0;
}
好了到这,我们的跳表结构已经写完了。
但在这最后这里,我想补充的是其实每一列竖向都是可以连通的,如图:
这样的结构的的确确存在着,有兴趣的小伙伴其实可以实现一下这样的结构。
再看一下,如果我们以头节点作为中心,顺时针旋转45度,有没有发现啥?
对,这是个极不平衡的二叉树!
看我下面的图
好了,这样就是我在这里总结的全部了。