入门基础
1、nginx(高性能服务器),操作系统(linux),缓存中间件(redis),网络协议,硬件驱动;//都是c语言写的;
c语言不会检查数组越界情况,设计初衷是为了性能;没有try ---catch机制;
错误:
运行时错误需要debug;打断点,f5(进入调试),断点位置未运行;
调试界面窗口布局:
字节为最小的寻址单位;
2、如何生成程序:
生成:预处理,编译,汇编,链接;
3、预处理
带参数的宏(简单的文本替换)不能以分号结尾
.i文件
如果不用括号括起来,3*FOO(5)预处理后就变了;
注意事项:
左括号应该紧贴宏函数的名称;
把整个宏函数表达式用括号括起来;FOO(5) (1+x+x*x);
应该把宏函数的每一个参数都添加小括号;
警惕宏函数导致的多次副作用;
\ :宏函数中反斜杠可以用于换行;
会打印一个world;因为if后面是两个语句;
解决:加大括号,用do{}while(0);
定义宏函数有多条语句,用大括号给括起来,只希望执行一次,用do while(0)循环;
宏函数的效率高,预处理阶段就直接进行文本替换,所以使用宏函数;
普通函数调用有额外的开销:
调用函数,保存寄存器的值,传递参数,保存下一条指令的地址;
函数返回:传递返回值,恢复寄存器的值;
4、编译:把c语言源代码翻译成汇编代码;
godbolt.org//在线的编译器平台;
5、汇编和链接
6、进程的虚拟内存空间
正在运行的程序叫进程;
栈向下生长,堆向上生长;
地址为0的区间不可访问,代表空指针NULL;
cpu进入内核态才可以访问内核虚拟储存器,假如是32位机器,内核的地址就是2的32次方-1;
7、变量和常量
变量的本质一片内存空间,在程序运行期间可以改变的量;
如何引用这一块区域,用变量名访问;
这块内存区域有多大,以及该如何解释这篇内存区域的数据:变量类型,
这片内存区域表示怎样的数据,值;
常量在程序运行期间不可以发生改变的量;
#define N 5//替换
const int m = 5;//用一片不可以修改的内存空间储存;
m和N是常量;
常量和常量表达式的区别:
常量表达式,在编译期间可以直接求值的表达式(consrexpr)
#define N 5 //常量表达式
const int m = 5 //c语言中认为这不是常量表达式,是常量
常量表达式可以指定数组长度,还可以用于switch语句的标签;
const int *p;//(pointer to const int)p是变量,从右往左看,指针,指向const int 类型,指向的对象不能修改;
int * const p;//(const pointer) 右往左 ,p的值不能修改,指向对象的类型可以修改;
8、标识符和关键字:
标识符(identifer):为变量,宏,函数等起的名字;
rules:只能包含字母、数字、下划线;不能以数字开头;
规范(conventions):单词与单词以下划线分割,symble_table;
驼峰命名法:symbleTable;
好的标识符:见名知义,
注意事项:标识符区分大小写,且不能与关键字发生冲突;
关键字:对c语言有特殊意义的名称,了解c_renferrnce;
IDE中关键字会以特殊颜色进行标识;
9、格式化输入和输出
最简单的计算机:输入设备,运算器,输出设备;
输入和输出模型:
核心矛盾:cpu,内存,i/o设备,在处理数据存在的速度差异;
cpu、内存:高速缓存(cache);
内存、i/o设备:缓冲区(buffer);缓存(redis);
格式化输出(printf):(f代表format格式化的意思)
函数原型:int printf(格式化字符串,表达式1,表达式2)
显示格式串中的内容,并在该字符串指定的位置插入要显示的值;
格式化字符串:普通字符 直接输出
转换说明 以%开头的,标识一个占位符,以后面表达式的值替换占位符;
%d:十进制形式输出整数;decimal(十进制的)以整数的形式解释这片内存空间并以十进制的形式输出;
%f:浮点数;float;以浮点数的形式解释这片空间,并输出;
转换说明:以何种方式解释内存区域,以何种格式输出;
·
10、二维数组:
11、时间复杂度
f(n), g(n) ,是N+(正整数) ->R(实数)+的函数
f = O(g);f的增长速度不大于g,类比f<=g;
当f(n) <=c*g(n);//c是常数,当n足够大,f = O(g)
例子:
诀窍:可以忽略系数,
使用递归和循环来实现斐波那契数列;
采用递归使用的时间比循环多;
递归的时间复杂度是指数级别,循环的是线性时间复杂度O(n)系数可忽略;
能否利用通项公式求解斐波拉契数列?:
不能计算,当该数列比较大的时候,浮点数是不精确的(浮点精度不断逼近);
线代矩阵求解斐波拉契数列(矩阵的n次幂可以在对数时间复杂度内求解):
12、汉诺塔问题:
代码实现:
(1)什么时候考虑使用递归?
一个大问题可以分解成若干个小问题,且小问题的求解方式和大问题的求解方式一致(递);
可以将这些小问题的解合并成大问题的解;(归)
这种情况可以考虑使用递归:
(2)使用递归需要注意的问题:
边界条件,避免stackover flow问题,stackover flow;
可以查找很多问题;
是否存在重复计算;
(3)如何写递归
边界条件;
递推公式;
13、约瑟夫环问题:
2*(1) -1;
2*(2(1)-1) -1;
---------------------------------------------------------------------------------------------
14、指针
计算机的最小寻址单位是字节;
变量的地址是变量第一个字节的地址;
指针就是地址;
指针变量:存放地址的变量;有时候把指针变量称为指针
指针变量只是存放了变量的首地址,怎么通过指针访问指针指向的对象;
声明指针时需要指明基础类型,
int(基础类型) *p ;//{变量名为p,变量类型为int *}
指针的两个基本操作:&和*
取地址运算符:&;
解引用运算符,*; // *有两个用途:一个声明指针,一个作为解引用运算符;
*(通过指针访问指针指向的对象);
野指针问题:
野指针:未初始化的指针或指向未知区域的指针;
注意事项:对野指针进行解引用运算会导致未定义的行为;
1、指针变量赋值
使用取地址运算符,p = &a;
通过另一个指针变量q赋值,p = q;
注意:*p = *q;p = q;
指针作为参数传递;
指针的好处,值传递不够改变实参的值,而通过传递实参的地址,可以通过指针改变实参的值;
练习:找出数组最大元素,和最小元素;
数组不能作为返回值;
-------------------------------------------------------------------------------
1、指针的算数运算,当指针指向数组元素时,可以通过指针的算术运算访问数组的其他元素;
指针加上一个整数,
指针减去一个整数,
两个指针相减(指向同一个数组里面的元素);
指针的算数运算是以元素大小为单位,而不是以字节为单位;
两个指针相减(指针的比较运算,两个数组指向同一数组元素)
2、指针处理数组
&arr[10]:只会计算arr[10]的地址,不会访问arr[10],不会发生数组越界;
(1)*和++的结合(考虑优先级(后置++优先级高))
*p++,*(P++) 表达式的值*p,副作用p自增;
(*p)++ 表达式的值为*p,副作用*p自增;
*++p,*(++p) 表达式的值为*(p+1),副作用p自增;
++*p,++(*p) 表达式的值为*p+1,副作用*p自增;
*和--也有类似用法;
函数返回值不能是数组类型;
程序如何终止:
操作系统调用main函数程序的开始;
main函数把状态码返回给操作系统;
如果不想再main函数中终止程序,exit函数;
指针的运算是根据元素的大小来运算;
3、数组名可以作为指针:(数组名可以作为指向索引元素为0的指针)
数组作为指针时是一个指针常量;
把数组名作为指针使用时可以简化代码;
把数组作为参数传递时就已经把数组当作指针了;
函数中的数组传递是值传递,所以可以修改;
指针也可以作为数组来使用,可以对指针使用[]运算符,p[i] = *(p + i);
总结:可以利用指针处理数组(指针的算数运算来处理)
数组名可以用作指向该数组索引为0的元素的指针
指针也可以作为数组名,可以对指针使用[]运算符,p[i] = *(p + i);
----------------------------------------------------------------------------------------
15、字符串
用双引号括起来的字符序列就是字符串字面量;编译器会把两个相邻的字符串字面量合并成一个字符串字面量;(相邻:当两个字符串字面量仅以空白字符分割时,就认为他们是相邻的)
字符串是怎么储存的:
c语言用字符数组来储存字符串字面量
空字符串也会占用一个字节的空间
printf("Hello \n");//对于priintf来说传递的是一个字符指针char *;
========================
字符串变量
c语言没有专门的字符串类型,c语言的字符串依赖于字符数组存在;
没有指定字符后面会被初始化成0;
字符串的初始化:(字符串大小刚好到数组空间大小是不会储存\0);
字符串的初始化和字符指针的初始化的区别;
==============
读写字符串
写:printf +%s;
puts(string);//puts写完字符串后会在后面添加额外的换行符;
忽略警告
读: scanf +%s;//scanf会跳过前面的空白字符,读取字符存入到数组中,直到遇到空白字符为止,然后会在后面添加\0;一般不用scanf
注意事项:永远不会包含空白字符;scanf不会检查数组越界
gets;读到换行符停止,在后面添加空字符\0,不会跳过空白字符,读取字符并且存入到数组中,
注意事项:也不会检查数组是否越界;
n表示数组的长度;所以最多读取n-1个字符;
========================
字符串的库函数:
(1)size_t strtlen(const char *s);指明在strlen中不会修改s指向的内容,传入参数);
计算字符串的长度不包含空字符;
(2)int strcmp(const char *s1,const char *s2);
按字典顺序比较s1和s2,“abc” <"cba","abc"<"abd","abc"<"abcd";
如果s1>s2 ,返回正整数;
如果s1=s2,返回零;
s1< s2,返回负整数;
(3)char *strcpy (char *s1,const char *s2);//把s2指向的字符串复制到s1指向的数组中;
const *s1,
char *strcat (char *dest,const char *src);
把字符串src 的内容追加到dest的末尾,并返回dest(不会检查数组越界);
char * strncat (char *dest ,const char *src,size_t count);
strncat 会写入\0;
所以一般这样调用strncat(s1,s2,sizeof(s1)-strlen(s1)-1);-1预留给\0位置;
strlen:
strcat:
==================================
字符串数组:
如何表示:
===========================
结构体:
c语言结构体相当于其他高级语言中的类,c语言只能在结构体中定义数据;
如何表示一个学生对象:
属性,学号,姓名,性别,成绩;
初始化:类似于数组
获取成员:S1.name;获取成员;赋值S3 = S1;
当结构体作为参数或者返回值时,会拷贝整个结构体中的数据;
为了避免拷贝数据,一般会传递一个指向结构体的指针;
c语言程序一啊不能是通过指针去引用结构体的,c语言提供了一个特别的运算符->;
可以使用typedef为结构体取别名;
===========================================
枚举enum:
在程序有一些变量只能取一些离散的值,如扑克牌的花色,
一种做法,定义一些宏:
enum suit {SPADE,HEART,CLUB};
可以使用typedef取别名;
枚举类型的值本质上都是整数值;
一般采用默认递增策略;
=============================
指针的高级应用:
动态内存分配;链式结构的基础;
指向指针的指针(二维指针);
指向函数的指针(函数指针);
--------------------------------------------------------
动态内存分配:
在头文件<stdlib.h>中定义了三个动态内存分配的函数(在堆上分配空间)
(1) void *(通用指针) malloc(size_t size)(memory allocate);
分配size个字节的内存,不会内存块清零,若分配不成功,返回空指针;
(2)void * calloc(size_t num,size_t size);
为num个元素分配内存空间,每个元素的大小为size个字节,并对内存块清零,分配不成功返回空指针;
(3)void *realloc (void * ptr,size_t new_size);
调整先前分配内存块的大小,如果分配成功返回指向新内存的指针,否则返回空指针;
注意:ptr应该指向先前使用动态内存分配函数分配的内存块;
空指针:不指向任何对象的指针;(用宏NULL表示,其值为0)
野指针:未初始化,指向未知区域的指针叫野指针;
:使用指针复制,可以避免strlcpy,strcat两次搜索这个字符串的末尾。可以效率更高;
释放内存空间:
如果申请的内存空间没有释放,就可能造成内存泄露现象;
内存泄漏,如果程序中存在垃圾,这种现象叫做内存泄漏;
如何避免内存泄漏:及时释放无用的内存块;
void free(void *ptr);
ptr 必须是先前调用malloc,calloc,realloc返回的指针;
ptr只是指向申请内存块的首地址,free函数如何知道该释放多大的内存空间?;
操作系统内存管理的时候留出了部分内存来存储malloc时的大小;
使用free函数虽然可以避免内存泄漏,但是也会引入一个新的问题:悬空指针;
悬空指针是非常难发现的,释放指针p,会导致所有指向相同内存块的指针悬空;
=================================================
链表:
用一条链把所有结点串联起来;
结点:
数据域
指针域:存放另一个结点的地址;
链表的分类: 循环链表在实际生产中用得比较少,循环链表在处理环状数据时会特别有用(约瑟夫环);
head指向的是边界结点,sentinal(哨兵);dummy node(聋哑结点);这个结点不存数据;
单链表的实现与遍历:
=====================================
二级指针:
--------------------
函数指针:指向函数的指针,函数也会有地址;
对任意一个函数返回值为double,参数类型double,给定两点a,
b求f((a+b)/2);
----------------------------
qsort;//快速排序
可以对任意类型的数组进行排序,不管元素类型;
排序的前提:比较;
排序的目的:查找;
ptr:指向被排序的数组;
count:数组元素的个数;
size:元素的大小;
comp:比较函数;//如果第一个参数大于第二个参数返回正值,如果第一个参数等于第二个参数返回0,如果第一个参数小于第二个参数,返回负值;
(strcmp(按字典顺序排序);
<string.h>)
通过宏计算数组个数;
=========================
数据结构:数组和链表是构建其它更复杂数据结构的基础
分治思想:递归,快速排序,
-----------------------------------
链表:
链表的基本操作:
单链表:增(在某个结点后面添加)O(1);
删(删除某个结点后面的结点)O(1);
查:根据索引查找值,O(n), 可以给结点编号;;查找与特定值相等的结点:大小有序O(n),大小无序O(n);
------------------------------------
双向链表:有两个指针域
除了单链表的基础还有额外的操作;
增(在某个结点前面添加)(O(1),单链表O(n));
删(删除某个结点)(O(1),单链表O(n));
查:(1):查找前驱结点;(O(1),单链表O(n));
(2):根据索引查找值,(O(n),平均遍历n/4个元素,单链表平均遍历n/2)//双链表记录索引值,比中间值大就逆序遍历,小则顺序遍历;
(3):查找与特定值相等的结点,:
a,大小有序(O(n),记录上一次查找的结点,平均遍历n/4,单链表平均遍历n/2);
b,大小元素 (O(n));
总结:虽然双向链表占用更多内存空间,但是在很多操作上面是优于单链表;
在实际生产中更倾向使用双向链表;LRU算法(双向链表);
思想:用空间换取时间;
缓存就是用空间换取时间的技术(缓存淘汰策略,FIFO,LFO(访问频率高的重要least frequently used),LRU(最近访问的数据才是重要的least reaently used);
用链表实现LRU:
添加元素:尾结点是最近一直没有访问的数据
a,元素存在;删除元素所在的结点,在第一个结点前面添加;
b、元素不存在;
(1)缓存满了,删除尾结点,在第一个结点前面添加
(2)缓存未满,在第一个结点前面添加,
查找元素是否存在:时间复杂度(O(n));添加删除都是O(1);
通过哈希表在O(1)时间复杂度上查找LRU改进算法决定元素是不是存在,同过链表进行添加删除;
------------------------------------
链表的实现增(头插,尾插)删 查:
实现:
//链表的实现
#include "linkedlist.h"//以当前目录为相对目录去查找
#include <stdio.h>
#include <stdlib.h>
//头插法
linkedlist* create_list() {//创建空链表
//空链表没有结点,创建一个结构体即可;都为0
//使用calloc创建,创建好空间自动清零;
return (linkedlist*)calloc(1, sizeof(linkedlist));
}
void add_before_head(linkedlist* list, int val)
{
//创建新结点
Node* newNode = (Node*)malloc(sizeof(Node));
if (NULL == newNode)
{
printf("Error:malloc failed in add_before_head!");
exit(1);
}
//初始化结点
newNode->val = val;
newNode->next = list->head;
list->head = newNode;//更新头结点
//判断链表为空
//判断head和tail是否为空,判断size是否为0
if (list->tail == NULL)
{
list->tail = newNode;
}
//更新size;
list->size++;
}
//写完一个接口就进行单元测试;
void add_behind_tail(linkedlist* list, int val) {//尾插法
//创建新结点
Node* newNode = (Node*)malloc(sizeof(Node));
if (NULL == newNode)
{
printf("Error:malloc failed in add_before_head!");
exit(1);
}
//初始化结点
newNode->val = val;
newNode->next = NULL;
//判定链表是否为空
if (0 == list->size) {
list->head = newNode;
list->tail = newNode;
}
else {
//链接新结点
list->tail->next = newNode;
//更新list->tail
list->tail = newNode;
}
list->size++;
}
void add_node(linkedlist* list, int index, int val) {//index表示任意索引位置,给链表一个虚拟的索引;
//判断index是不是合法;
if (index < 0 || index > list->size) {
printf("Error:illegal index!\n");
exit(1);
}
//创建新结点
Node* newNode = (Node*)malloc(sizeof(Node));
if (NULL == newNode)
{
printf("Error:malloc failed in add_before_head!");
exit(1);
}
newNode->val = val;
if (0 == index)//考虑头结点情况,在头节点前面添加
{//头插法
newNode->next = list->head;
list->head = newNode;
//更新尾结点
}
else {
//找到索引为index -1的结点,循环开始之前和之后保证i和p总是对应起来的;
//在目标索引的前面插入;
Node* p = list->head;
for (int i = 0; i < index - 1; i++)
{
p = p->next;//p和i同时增
}//循环结束后i = index -1;p指向index-1的结点;
newNode->next = p->next;//有->相当于拷贝下一结点的地址
p->next = newNode;//相当于拷贝本结点的地址;
}
//更新尾结点
if (index == list->size)
{
list->tail = newNode;
}
list->size++;
}
bool delete_node(linkedlist* list, int val) {//删除与val相等的结点返回是否删除成功的状态
//找到前驱结点
Node* prev = NULL;//前驱结点
Node* curr = list->head;//现在的结点,表示第一个结点
//寻找前驱结点
while (curr != NULL &&curr->val != val)//&&的短路原则;
{
prev = curr;
curr = curr->next;
}
//没有这样的元素
if (curr == NULL)
{
return false;
}
//删除第一个元素
if (prev == NULL)//第一个元素
{
if (list->size == 1) {//只有一个元素的情况
list->head = list->tail = NULL;
}
else
{
list->head = curr->next;//list->head指向原list->head的后一个结点
}//curr可以用list->head替代
free(curr);
}
else//不删第一个元素
{
prev->next = curr->next;
if (prev->next == NULL)//更新尾结点
{
list->tail = prev;
}
free(curr);
}
list->size--;
return true;
}
int indexof(linkedlist* list, int val) {//查找,看链表中与指定值val相等的结点的索引,没有这样的结点返回-1,
//找出第一个与val相等的结点保证指针和结点对应
Node* curr = list->head;
for (int i = 0; i < list->size; ++i,curr = curr->next) {//curr->next,是获取curr指向的结点的下一个结点的意思;
if (curr->val == val) {
return i;
}
}
//没有找到
return -1;
}
void destroy_list(linkedlist* list) {
//释放结点空间遍历链表
Node* curr = list->head;
while (curr != NULL) {
Node* next = curr->next;//记录后继结点
free(curr);
curr = next;
}
//释放linkedlist结构体
free(list);
}
未考虑curr->next为空,也就是释放到最后一个结点的情况;
----------------------------------
接口:
//接口,实现的功能集合
//链表的增加,删,查
#include <stdbool.h>
typedef struct node_s {
int val;
struct node_s* next;
}Node;
typedef struct linkedlist {//保存链表的头结点和尾结点,长度
Node* head;
Node* tail;
int size;
}linkedlist;//当作一个链表
linkedlist* create_list();//无参构造方法,创建一个空链表
void destroy_list(linkedlist* list);//析构方法,释放堆内存空间;
void add_before_head(linkedlist* list, int val);
void add_behind_tail(linkedlist* list, int val);
void add_node(linkedlist* list, int index, int val);//index表示任意索引位置,给链表一个虚拟的索引;
bool delete_node(linkedlist* list, int val);//删除与val相等的结点返回是否删除成功的状态
int indexof(linkedlist* list, int val);//查找,看链表中与指定值val相等的结点的索引,没有这样的结点返回-1,
-----------------------
测试:
//测试
#include "linkedlist.h"
#include <stdio.h>
int main(int argc, char* const argv[]) {
linkedlist* list = create_list();
//add_before_head(list,1);
//add_before_head(list,2);
//add_before_head(list,3);
//add_before_head(list,4);
//
add_behind_tail(list, 1);
add_behind_tail(list, 2);
add_behind_tail(list, 3);
add_behind_tail(list, 4);//1-2-3-4;
//delete_node(list, 1);
//add_node(list, 0, 1);
//add_node(list, 0, 2);
//add_node(list, 1, 3);
//add_node(list, 3, 4);//2->3->1->4
printf("%d\n",indexof(list,3));
printf("%d\n", indexof(list, 5));
return 0;
}
---------------------------
作业:
查单链表的中间元素:
判断链表是否有环:
1、只能判断头尾结点循环,
2、
1、遍历单链表,做标记,直到再次遇到标记;结构体不能改变,用容器去遍历结点,没有遍历过就存入该容器,(怎么在O(1)的时间复杂度内完成)考虑哈希表;
2、快慢指针:
看能否回到起点:
看指针是否会相遇:
3、反转单链表:
头插法:
递归:
---------------------------------------------------------------------
栈:一种受限的线性表,只能在栈顶添加和删除元素;LIFO(last in first out);
作用:
函数调用;
深度优先遍历:
浏览器的前进后退;http不会记录上一次访问的数据,(用两个栈实现,浏览下一个就把当前页面放入另一个栈,默认显示栈的栈顶层面);
括号匹配问题;{()};把左括号看作入栈,右括号看作出栈;
后缀表达式求值;遇到操作数入栈,遇到操作符弹出两个操作数计算,再把结果入栈继续计算;
栈的基本操作:
push:入栈
POP:出栈
isEmpty:判断是否为空
peek:查看栈顶(top)元素;
栈的实现:用数组实现栈:用数组首地址实现栈底;
数组实现栈代码:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define MAX_SIZE 100
int stack[MAX_SIZE];
int top = -1;
void push(int val);
bool isEmpty();
int pop();
bool isFull();
int peek();
int main(int argc, char const *argv[])
{
push(1);
push(2);
push(3);
// 获取并打印栈顶元素
printf("Top element: %d\n", peek());
// 出栈并打印出栈元素
printf("Popped element: %d\n", pop());
printf("Popped element: %d\n", pop());
return 0;
}
void push(int val){
if (isFull())
{
printf("stack is full!\n");
exit(1);
}
stack[++top] = val;
}
bool isEmpty(){
if (top == -1)
{
return true;
}
return false;
}
int pop(){
if (isEmpty())
{
printf("stack is empty!\n");
exit(1);
}
return stack[top--];
}
int peek(){
if (isEmpty()) {
printf("Stack is empty!\n");
exit(1);
}
return stack[top];
}
bool isFull(){
if (top == MAX_SIZE -1)
{
return true;
}
return false;
}
-------------------------------------------------------------------------------
队列:队列是一种操作受限的线性表
一端插入--》队尾
另一端删除--》队头
特性:FIFO(first in first out)
作用:
广度优先遍历;
缓存(消息中间件)rabbitmq,,,katka;
基本操作:
enqueue(入队列):
dequeue(出队列):
peek(查看队头):
isEmpty(判断空):
isFull(判断满):
用链表实现队列:
用循环数组实现队列时要空一个元素;
(循环数组实现队列时,通常会保留一个空位,也被称为“哨兵位”(sentinel)或者“虚拟头尾”(dummy head and tail)。保留一个空位的好处在于,可以简化队头和队尾的插入和删除操作。
入队操作(Enqueue):
在队列的尾部添加元素,如果队列已满(即队尾指针加1后等于队头指针),那么就需要将队列扩大一倍(通常是通过动态扩容实现),并更新队尾指针。
如果队列未满,直接将元素插入队尾,并将队尾指针加1。
出队操作(Dequeue):
从队列的头部删除元素,如果队列为空(即队头指针等于队尾指针),那么就返回错误或者异常。
如果队列非空,直接将队头元素删除,并将队头指针加1。
保留一个空位的好处是,在出队和入队操作时,不需要判断队列是否为空或者是否已满,简化了一部分逻辑。同时,对于循环队列来说,有一个空位也可以更清晰地表明队列的状态。例如,如果队头指针等于队尾指针,那么可以明确地知道队列为空;如果队尾指针的下一个位置等于队头指针,那么可以明确地知道队列已满。)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100
int queue[MAX_SIZE];
int front = 0;
int rear = 0;
bool isEmpty();
bool isFull();
int peek();
void dequeue();
void enqueue(int val);
int main(int argc, char const *argv[])
{
enqueue(1);
enqueue(2);
enqueue(3);
enqueue(4);
printf("%d\n",queue[front]);
dequeue();
printf("%d\n",queue[front]);
printf("%d\n",peek());
return 0;
}
void enqueue(int val){
if (isFull())
{
printf("queue is full!\n");
exit(1);
}
queue[rear] = val;
rear = (rear+1) % MAX_SIZE;
printf("Element %d enqueued to queue successfully!\n",val);
}
bool isFull(){
if ((rear + 1) % MAX_SIZE == front)
{
return true;
}
return false;
}
void dequeue(){
if (isEmpty())
{
printf("queue is empty!\n");
exit(1);
}
int data = queue[front];
front = (front + 1) % MAX_SIZE ;
printf("Element %d dequeued from queue successfully\n",data);
}
bool isEmpty(){
if (front == rear)
{
return true;
}
return false;
}
int peek(){
return queue[front];
}
-----------------
代码改进:
//队列的基本操作
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define N 10
typedef struct queue_s {
int elements[N];
int front;
int rear;
}Queue;
//创建空队列
Queue* Queue_create();
void Queue_destroy(Queue* q);
void enqueue(Queue* q, int val);
int dequeue(Queue* q);
int peek(Queue* q);
bool isEmpty(Queue* q);
bool isFull(Queue* q);
int main(void) {
Queue* q = Queue_create();
for (int i = 0; i < N -1; i++) {
enqueue(q, i);
}
for (int i = 0; i < N -1; i++) {
printf("%d", dequeue(q));
}
return 0;
}
Queue* Queue_create() {
return (Queue*)calloc(1, sizeof(Queue));
}
void Queue_destroy(Queue* q);
void enqueue(Queue* q, int val) {
if (isFull(q)) {
printf("Error: queue is full!\n");
exit(1);
}
q->elements[q->rear] = val;
//更新rear
q->rear = (q->rear + 1) % N;
}
int dequeue(Queue* q) {
if (isEmpty(q)) {
printf("Error:queue is empty!");
exit(1);
}
int data = q->elements[q->front];
q->front = (q->front + 1) % N;
return data;
}
int peek(Queue* q) {
if (isEmpty(q)) {
printf("Error:queue is empty!");
exit(1);
}
return q->elements[q->front];
}
bool isEmpty(Queue* q) {
return q->front == q->rear;
}
bool isFull(Queue* q) {
return q->front == (q->rear + 1) % N;
}
-----------------
采用链表实现队列:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct node_s{
int val;
struct node_s* next;
}Node;
typedef struct LinkedList{
Node* head;
Node* tail;
int size;
}LinkedList;
LinkedList* createLinkedList();
void enqueue(LinkedList* list,int val);
bool dequeue(LinkedList* list);
int peek(LinkedList* list);
bool isEmpty(LinkedList* list);
void printfQueue(LinkedList* list);
int main(int argc, char const *argv[])
{
LinkedList* list = createLinkedList();
enqueue(list,1);
enqueue(list,2);
enqueue(list,3);
enqueue(list,4);
printfQueue(list);
dequeue(list);
printf("%d\n",peek(list));
printfQueue(list);
return 0;
}
LinkedList* createLinkedList(){
return (LinkedList*)calloc(1,sizeof(LinkedList));
}
void enqueue(LinkedList* list,int val){
Node* new_node = (Node*)malloc(sizeof(Node));
if (NULL == new_node)
{
printf("Error:malloc failed in enqueue!\n");
exit(1);
}
new_node->val = val;
new_node->next = NULL;
if (isEmpty(list))
{
list->head = new_node;
list->tail = new_node;
}else{
list->tail->next = new_node;
list->tail = new_node;
}
list->size++;
}
bool dequeue(LinkedList* list){
Node* curr = list->head;
if (isEmpty(list))
{
printf("Error:queue is empty!\n");
return false;
}
if (list->size == 1)
{
list->head = list->tail = NULL;
}
else{
list->head = curr->next;
}
if (list->head->next == NULL)
{
list->tail = list->tail;
}
free(curr);
list->size--;
return true;
}
int peek(LinkedList* list){
return list->head->val;
}
bool isEmpty(LinkedList* list){
if (list->head == NULL)
{
return true;
}
return false;
}
void printfQueue(LinkedList* list){
Node* curr = list->head;
while(curr){
printf("%d\n",curr->val);
curr=curr->next;
}
}
---------------------------------------------------------
哈希表:
统计每个小写字母出现的次数
申请大小为26的数组:
val get(key);更新key的值-》O(1)
void put(key ,val);增加键值对->O(1)
void remove(key);删除键值对;->O(1)
键:范围不大;键可以转化成数组的索引;key-‘a’;
哈希函数作用:打散键值对,让键值对尽量平均地分布;
哈希表的两个问题:
(1)哈希函数:让键值对尽可能地平均分布
(2)解决冲突:拉链法
数组存放的是指针,指向链表的头结点;
哈希表的基本操作:
val get(key);根据key获取值;
用哈希函数算出索引:int index = hash(key);
遍历链表:key存在返回key对应的val;如果key不存在返回特殊值;
val put(key,int val);添加键值对
用哈希函数算出索引:int index = hash(key);
遍历链表:
key存在:更新key对应的val,并返回原来的val;
如果key不存在:添加键值对,返回特殊值;
val remove(key);删除键值对
用哈希函数算出索引:int index = hash(key);
遍历链表:
key存在,删除键值对,并返回删除对应的值;
key不存在,返回一个特殊值;
-----------------
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
typedef char* K;//
typedef int V;
#define N 10
typedef struct entry {//键值对
K key;
V val;
struct entry_s* next;
}Entry;
typedef struct {
Entry* table[N];
}HashMap;
HashMap* HashMap_create();
void HashMap_destory(HashMap* map);
V HashMap_get(HashMap* map,K key);
V HashMap_put(HashMap* map,K key, V val);
V HashMap_remove(HashMap* map,K key);
HashMap* HashMap_create() {
return (HashMap*)calloc(1, sizeof(HashMap));
}
int hash(char* key) {
int h = 0, g;
while (*key) {
h = (h << 4) + *key++;
g = h & 0xf0000000;
if (g)
h ^= g >> 24;
h &= ~g;
}
return h % N;
}
void HashMap_destory(HashMap* map) {//释放整个哈希表
//先释放链表再释放数组;
//释放所有结点
for (int i = 0; i < N; i++)
{
Entry* curr = map->table[i];
while (curr != NULL)
{
Entry* next = curr->next;
free(curr);
curr = next;
}
}
//释放数组HahhMap结构体
free(map);
}
V HashMap_get(HashMap* map,K key) {
int index = hash(key);//根据key获取索引
//遍历链表
Entry* curr = map->table[index];
while (curr != NULL) {
if (strcmp(key, curr->key) == 0) {//判断key是否存在
return curr->val;
}
curr = curr->next;
}
//不存在这样的key;
return -1;
}
V HashMap_put(HashMap* map,K key, V val) {//如果key存在替换该值,并返回原来的值,存在则返回特殊值;
int index = hash(key);//根据key获取索引
//遍历链表
Entry* curr = map->table[index];
while (curr != NULL) {
//strcmp比较函数相等返回0;第一个字符串大于第二个返回正数
if (strcmp(key, curr->key) == 0) {//判断key是否存在
V old_value = curr->val;
curr->val = val;
return old_value;
}
curr = curr->next;
}
//不存在这样的key;添加结点;头插法
//创建键值对,
Entry* entry = (Entry*)malloc(sizeof(Entry));
if (NULL == entry)
{
printf("Error:malloc failed in HashMap!");
exit(1);
}
entry->key = key;
entry->val = val;
entry->next = map->table[index];
//更新链表的头结点
map->table[index] = entry;
return -1;
}
V HashMap_remove(HashMap* map, K key) {
int index = hash(key);//根据key获取索引
//遍历链表
Entry* prev = NULL;
Entry* curr = map->table[index];
while (curr != NULL) {
if (strcmp(key, curr->key) == 0) {//判断key是否存在
//删除结点
if (prev == NULL)
{
map->table[index] = curr->next;
}
else {
prev->next = curr->next;
}
//释放空间
V removeVal = curr->val;
free(curr);
return removeVal;
}
prev = curr;
curr = curr->next;
}
}
int main(void) {
HashMap* map = HashMap_create();
HashMap_put(map, "liu", 1);
HashMap_put(map, "yi", 2);
HashMap_put(map, "fei", 3);
printf("%d\n", HashMap_get(map, "liu"));
HashMap_remove(map, "yi");
printf("%d\n", HashMap_get(map, "liuyifei"));
return 0;
}
-----------------------------------------------------------------------------
二叉搜索树(BST)一对多关系层次结构
Tree
族谱:Family Tree
第一个结点称为根结点
父结点;孩子结点;叔叔结点;根结点;
叶子结点:没有孩子的结点;NULL结点;(不同的语境有不同含义);
二叉树:每个结点最多有两个孩子的树
没有根节点叫空树;
二叉树的性质:
如果一棵树有n层,最多有
特殊的二叉树:
(1)满二叉树:
(2)完全二叉树:每个结点都有两个子结点,除了最底层结点之外。
最底层的结点集中在二叉树的左侧,其他层的结点按层次顺序向右排列;
二叉树可以用数组表示:
数组表示浪费空间:(只有结点多的时候才用数组,例如完全二叉树)
二叉树的遍历:
深度优先遍历:
先序遍历:(D(根节点)L(左子树)R(右子树))
代码实现:
中序遍历:(LDR)
后序遍历:(LRD)
广度优先遍历(层级遍历):
---------------------------------
二叉树的建树(根据先序后序中序来建树):
中序和任意一种都可以建树:
(找出左子树的先序和中序,再找右子树的先序和中序;先序确定根节点,中序找到根结点的左右结点);先序和中序;
先序和后续不能区分左右子树;
代码:
-----------------------------排序的目的一般是 查找;二分查找静态数据是最快的,但是依赖于数组;
二叉查找树(Binary Search Tree),又叫二叉排序树查找效率依赖于树的高度;
1、二叉树
2、左子树所有结点的key值都小于根节点的key值;
右子树所有结点的key值都大于根节点的key值;
并且左、右子树都是二叉查找树;
1、如何判断二叉树是不是BST?
判断是不是二叉查找树中序遍历后顺序即为排好序的树,只需判断中序遍历后的序列是不是升序;
2、BST的好处:查找快
3、如果一棵BST有n个结点,它最小是多少?
完全二叉树:
4、一棵高度为h的完全二叉树,他的结点数目的范围为?
普通的二叉查找树的高度不一定是O(logn)这个级别;
构建O(logn)这个级别
平衡二叉查找树:
AVL树:对任意一个结点,它的左子树的高度和右子树的高度相差不超过1;查找性能比红黑树好
红黑树,保证树的高度是O(logn)级别;
------------------------------
2-3-4树:每个结点的子结点可以有2 || 3 || 4个;
在普通的二叉查找树上进行了扩展,允许有多个键key(1-3),树保证完美平衡(根到每一个叶子结点的路径都是一样长)
有几个孩子就称为几个度;
查找:
插入:
影响完美平衡:
插入H:
从底部向上分裂直到有空间为止;
top---down:
分裂根结点树的高度加1;
2-3-4树性能分析:
2-3-4树的实现:
red——black trees
用BST来表示2-3-4树;
利用红黑树来实现2-3-4树;
用内部红色的边来表示3node和4node;
不能有连续的两条红色的边;用子节点的颜色来表示边的颜色;
普通的红黑树:
1、左倾红黑树(三结点的红色边是左倾的)
旋转
用队列实现广度优先遍历
代码实现:
#include <stdbool.h>
#define RED false
#define BLACK true
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define N 10
typedef struct TreeNode_s TreeNode;
typedef TreeNode* E;
typedef struct {
E elements[N];
int front;
int rear;
} Queue;
// 创建空的队列
Queue* Queue_create();
void Queue_destroy(Queue* q);
void enqueue(Queue* q, E val);
E dequeue(Queue* q);
E peek(Queue* q);
bool isEmpty(Queue* q);
bool isFull(Queue* q);
#define parent(x) ((x)->parent)
#define grandParent(x) ((x)->parent->parent)
typedef char T;
typedef struct TreeNode_s {
bool color;
T key;
struct TreeNode_s* left;
struct TreeNode_s* right;
struct TreeNode_s* parent;
}TreeNode;
typedef struct {
TreeNode* root;
} RBTree;
RBTree* RBTree_create();
void RBTree_insert(RBTree* tree, T key);
// 遍历
void RBTree_preOrder(RBTree* tree);
void RBTree_inOrder(RBTree* tree);
void RBTree_postOrder(RBTree* tree);
void RBTree_levelOrder(RBTree* tree);
// 建树
RBTree* RBTree_build(char* preOrder, char* inOrder,int len);
/*
* 对红黑树的结点(x)进行左旋转
*
*
* px px
* / /
* x y
* / \ --(左旋)--> / \
* lx y x ry
* / \ / \
* ly ry lx ly
*
*
*/
void rotate_left(RBTree* tree, TreeNode* x) {
TreeNode* y = x->right;
// 设置x的右孩子为y
x->right = y->left;
// 将 “y的左孩子” 设为 “x的右孩子”;
// 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
if (y->left != NULL) {
y->left->parent = x;
}
// // 将 “x的父亲” 设为 “y的父亲”
y->parent = x->parent;
if (x->parent == NULL) {
tree->root = y; // 如果 “x的父亲” 是空节点,则将y设为根节点
}
else {
if (x->parent->left == x)
x->parent->left = y;
else
x->parent->right = y;
}
y->left = x;
x->parent = y;
}
/*
* 对红黑树的结点(y)进行右旋转
*
* 右旋示意图(对结点y进行左旋):
* py py
* / /
* y x
* / \ --(右旋)--> / \
* x ry lx y
* / \ / \
* lx rx rx ry
*
*/
void rotate_right(RBTree* tree, TreeNode* y) {
TreeNode* x = y->left;
y->left = x->right;
if (x->right != NULL) {
x->right->parent = y;
}
x->parent = y->parent;
if (y->parent == NULL) {
tree->root = x;
}
else {
if (y->parent->left == y)
y->parent->left = x;
else
y->parent->right = x;
}
x->right = y;
y->parent = x;
}
void rbtree_insert_fixup(RBTree* tree, TreeNode* node) {
TreeNode* parent = node->parent;
while (parent != NULL && parent->color == RED) {
TreeNode* grandParent = parent->parent;
if (parent == grandParent->left) {
TreeNode* uncle = grandParent->right;
// case 1: 叔叔结点是红色
if (uncle != NULL && uncle->color == RED) {
uncle->color = BLACK;
parent->color = BLACK;
grandParent->color = RED;
node = grandParent;
continue;
}
// case 2: 叔叔结点是黑色, 且当前结点是右孩子
if (node == parent->right) {
node = parent;
rotate_left(tree, node);
}
// case 3: 当前结点是左孩子,且叔叔结点是黑色
grandParent(node)->color = RED;
parent(node)->color = BLACK;
rotate_right(tree, grandParent(node));
}
else {
TreeNode* uncle = grandParent->left;
// case 1: 叔叔结点是红色
if (uncle != NULL && uncle->color == RED) {
uncle->color = BLACK;
parent->color = BLACK;
grandParent->color = RED;
node = grandParent;
continue;
}
// case 2: 当前结点是左孩子,叔叔结点是黑色的
if (node == parent->left) {
node = parent;
rotate_right(tree, node);
}
// case 3: 当前结点是右孩子,叔叔结点是黑色
grandParent(node)->color = RED;
parent(node)->color = BLACK;
rotate_left(tree, grandParent(node));
} }
// 将根结点设为黑色
tree->root->color = BLACK;}
RBTree* RBTree_create() {
return (RBTree*)calloc(1, sizeof(RBTree));
}
void RBTree_insert(RBTree* tree, T key) {
TreeNode* prev = NULL;
TreeNode* curr = tree->root;
while (curr != NULL) {
prev = curr;
int cmp = key - curr->key;
if (cmp < 0)
curr = curr->left;
else if (cmp > 0)
curr = curr->right;
else
return;
}
// 找到了插入的位置, 创建新结点
TreeNode* node = (TreeNode*)calloc(1, sizeof(TreeNode));
if (node == NULL) {
printf("Error: malloc failed in RBTree_insert.\n");
exit(1);
}
node->key = key;
// node->color = RED;
// 插入结点
node->parent = prev;
if (prev == NULL)
tree->root = node;
else if (key < prev->key)
prev->left = node;
else
prev->right = node;
// 重新修订为一颗红黑树
rbtree_insert_fixup(tree, node);
}
void inOrder(TreeNode* root) {
// 边界条件
if (root == NULL) return;
// 遍历左子树
inOrder(root->left);
// 遍历根结点
printf("%c ", root->key);
// 遍历右子树
inOrder(root->right);
}
void RBTree_inOrder(RBTree* tree) {
// 委托这个方法实现
inOrder(tree->root);
}
void RBTree_levelOrder(RBTree* tree) {
if (tree->root == NULL) return;
Queue* q = Queue_create();
// 将根结点入队列
enqueue(q, tree->root);
while (!isEmpty(q)) {
// 出队列
TreeNode* node = dequeue(q);
printf("%c ", node->key);
if (node->left != NULL) {
enqueue(q, node->left);
}
if (node->right != NULL) {
enqueue(q, node->right);
}
}
}
TreeNode* build(char* preOrder, char* inOrder, int len) {
char ch = *preOrder;
// 构建根结点
TreeNode* root = (TreeNode*)calloc(1, sizeof(TreeNode));
root->key = ch;
int idx = 0;
for (; idx < len; idx++) {
if (inOrder[idx] == ch) {
break;
}
}
// ch[1..idx][idx+1, len-1]
// [0..idx-1]idx[idx+1, len-1]
// 构建左子树
root->left = build(preOrder + 1, inOrder, idx);
// 构建右子树
root->right = build(preOrder + idx + 1, inOrder + idx + 1, len - idx - 1);
return root;
}
RBTree* RBTree_build(char* preOrder, char* inOrder, int len) {
RBTree* tree = (RBTree*)calloc(1, sizeof(RBTree));
// 创建所有结点,并把创建后的根结点赋值给 tree->root;
tree->root = build(preOrder, inOrder, len);
return tree;
}
Queue* Queue_create() {
return (Queue*)calloc(1, sizeof(Queue));
}
void Queue_destroy(Queue* q);
void enqueue(Queue* q, E val) {
if (isFull(q)) {
printf("Error: queue is Full.\n");
exit(1);
}
q->elements[q->rear] = val;
// 更新rear的值
q->rear = (q->rear + 1) % N;
}
E dequeue(Queue* q) {
if (isEmpty(q)) {
printf("Error: queue is Empty.\n");
exit(1);
}
E removeValue = q->elements[q->front];
q->front = (q->front + 1) % N;
return removeValue;
}
E peek(Queue* q) {
if (isEmpty(q)) {
printf("Error: queue is Empty.\n");
exit(1);
}
return q->elements[q->front];
}
bool isEmpty(Queue* q) {
return q->front == q->rear;
}
bool isFull(Queue* q) {
return q->front == (q->rear + 1) % N;
}
#define SIZE(a) (sizeof(a)/sizeof(a[0]))
int main(void) {
RBTree* tree = RBTree_create();
char arr[] = { 'E', 'B', 'A', 'D', 'C'};
for (int i = 0; i < SIZE(arr); i++) {
RBTree_insert(tree, arr[i]);
}
RBTree_inOrder(tree);
printf("\n");
RBTree_levelOrder(tree);
printf("\n");
return 0;
}
----------------------------------------------------------------
习题:
1、
指针和数组在编程中有着密切的关系,它们在某些情况下可以互相转换。
首先,让我们定义一下指针和数组:
指针:指针是一个变量,其值为另一个变量的地址。在C和C++等语言中,我们可以通过使用"&"运算符获取一个变量的地址。
数组:数组是一种特殊的数据结构,可以存储相同类型的数据序列。我们可以通过索引访问数组中的特定元素。
接下来,我们来看看指针和数组之间的关系:
数组名是地址:在大多数编程语言中,数组名实际上是一个指向数组第一个元素的指针。因此,当我们说"数组名[i]"时,我们实际上是说"指向数组第一个元素的指针向后移动i个位置"。
可以使用指针访问数组元素:由于数组名是一个指针,我们可以使用指针来访问数组元素。例如,如果我们有一个名为"arr"的数组,我们可以使用"arr[i]"来访问第i个元素,这实际上等价于"*(arr + i)"。
指针可以作为数组长度:在C和C++中,如果我们有一个指针"p"指向数组的第一个元素,并且我们知道数组中元素的类型大小,那么我们可以通过计算"p + n"来获取数组的第n个元素,其中n是数组长度。这是因为指针加法是基于元素大小的,所以我们可以利用这个特性来计算数组长度。
动态分配内存:指针可以与动态内存分配函数(如malloc、calloc等)一起使用,以在运行时创建一个指定大小的数组。这对于需要动态大小的数组或数据结构非常有用。
指针数组:指针数组是数组的数组,每个元素都是一个指针。这可以用来创建二维数组或更复杂的数据结构。
总的来说,指针和数组之间的关系非常密切,它们在很多情况下都可以互相操作和使用。但是需要注意的是,不正确的使用指针可能会导致内存错误或程序崩溃,因此在使用指针时要格外小心。0
代码1:(有缺陷)
代码2:优化算法
2、
代码实现:
#include <stdio.h> #define N 10 int main() { int n; int num = 1; scanf("%d",&n); int matrix[N][N]; int i = 0,j = 0, p = n -1,q = n - 1; while(i <= p || j <= q){ for (int k = j; k <= q; ++k) { matrix[i][k] = num++; } i++; for (int k = i; k <= p; ++k) { matrix[k][q] = num++; } q--; if(i <= p) { // add this if statement for (int k = q; k >= j; --k) { matrix[p][k] = num++; } p--; } if(j <= q) { // add this if statement for (int k = p; k >= i ; --k) { matrix[k][j] = num++; } j++; } } // Now print the matrix for(i = 0; i < n; i++) { for(j = 0; j < n; j++) { printf("%d ", matrix[i][j]); } printf("\n"); } return 0; }
3、
#include <stdio.h> #include <string.h> #include <ctype.h> #define N 20 void seperate(char* str, int len); // seperate(分开) int main() { char str[] = "a1b2c3"; // 使用数组而不是指针来存储字符串 int len = strlen(str); puts(str); seperate(str, len); puts(str); return 0; } void seperate(char* str, int len){ char digits[N],alphas[N]; //digits(数字),alphas(阿尔法,alphascope(字母显示器)) int i = 0,j = 0; char* p = str; while(*p){ if(isdigit(*p)){ digits[i++] = *p; }else{ alphas[j++] = *p; } p++; } digits[i] = '\0'; alphas[j] = '\0'; strcpy(str, digits); //改变了拼接顺序,先拼接数字,再拼接字母 strcat(str,alphas); //strcat函数之间没有空格,直接拼接即可 }
4、
5、
6、
LRU算法:least recently used 页面置换算法,选择最近最久未使用的页面予以淘汰;向前看;
此代码表示如果内存中存在待入进程则把进程放到第一个位置,如果不存在且容量只有1,则置换即可,如果容量不为1,在最后一个位置插入;
代码修正:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CACHE_SIZE 100
typedef struct Node {
int key;
int value;
struct Node *prev, *next;
} Node;
typedef struct {
Node *head, *tail;
} DoublyLinkedList;
typedef struct {
int size;
DoublyLinkedList *list;
Node **hash;
} Cache;
DoublyLinkedList* createDoublyLinkedList();
Cache* createCache(int size);
void insertInList(DoublyLinkedList *list, Node *node);
void removeFromList(DoublyLinkedList *list, Node *node);
Node* searchInList(DoublyLinkedList *list, int key);
Node* searchInHash(Cache *cache, int key);
void moveToHead(DoublyLinkedList *list, Node *node);
void removeFromCache(Cache *cache, Node *node);
Node* createNode(int key, int value);
// Cache operations
Node* get(Cache *cache, int key);
void put(Cache *cache, int key, int value);
// Doubly Linked List operations
DoublyLinkedList* createDoublyLinkedList() {
DoublyLinkedList *list = malloc(sizeof(DoublyLinkedList));
list->head = list->tail = NULL;
return list;
}
void insertInList(DoublyLinkedList *list, Node *node) {
node->next = list->head;
node->prev = NULL;
if (list->head != NULL) {
list->head->prev = node;
} else {
list->tail = node;
}
list->head = node;
}
void removeFromList(DoublyLinkedList *list, Node *node) {
if (node->prev != NULL) {
node->prev->next = node->next;
} else {
list->head = node->next;
}
if (node->next != NULL) {
node->next->prev = node->prev;
} else {
list->tail = node->prev;
}
}
Node* searchInList(DoublyLinkedList *list, int key) {
Node *current = list->head;
while (current != NULL) {
if (current->key == key) {
return current;
}
current = current->next;
}
return NULL;
}
// Cache operations
Cache* createCache(int size) {
Cache *cache = malloc(sizeof(Cache));
cache->size = size;
cache->list = createDoublyLinkedList();
cache->hash = malloc(sizeof(Node*) * size);
for (int i = 0; i < size; i++) {
cache->hash[i] = NULL;
}
return cache;
}
Node* searchInHash(Cache *cache, int key) {
int index = hash(key) % cache->size;
Node *node = cache->hash[index];
while (node != NULL) {
if (node->key == key) {
return node; // Key found
}
node = node->next;
}
return NULL; // Key not found
}
// ...
// Cache operations
Node* get(Cache *cache, int key) {
Node *node = searchInHash(cache, key);
if (node == NULL) {
return NULL; // Key not found
}
moveToHead(cache->list, node); // Move the node to the head for the next access
return node->value;
}
void put(Cache *cache, int key, int value) {
Node *node = searchInHash(cache, key);
if (node != NULL) { // Key exists, update value
node->value = value;
moveToHead(cache->list, node); // Move the node to the head for the next access
return;
}
// Key doesn't exist, create a new node
if (cache->size == cache->list->head->key) { // If cache is full, remove LRU node
Node *lru = cache->list->tail;
removeFromList(cache->list, lru);
removeFromCache(cache, lru);
}
Node *newNode = createNode(key, value);
insertInList(cache->list, newNode);
searchInHash(cache, key); // This will insert the node at the correct position in the hash table
}
// Doubly Linked List operations
Node* createNode(int key, int value) {
Node *node = malloc(sizeof(Node));
node->key = key;
node->value = value;
node->prev = node->next = NULL;
return node;
}
// Cache operations - utility functions
void removeFromCache(Cache *cache, Node *node) {
for (int i = 0; i < cache->size; i++) {
if (cache->hash[i] == node) {
cache->hash[i] = NULL;
break;
}
}
}
void moveToHead(DoublyLinkedList *list, Node *node) {
removeFromList(list, node);
insertInList(list, node);
}
以上代码未考虑并发控制问题;
--------------------------------------
7、
先比较两个链表的第一个元素确定第一个位置,再把第二个元素和另一个链表的下一个元素或者本结点的下一个元素比较,当一个链表结束后直接在尾结点接入另一个链表剩下的元素;
代码实现: