数据结构和算法(四)


作业分析讲解
=============
    1.  代码的错误:
               语法错误:最容易的,gcc编译的时候编译器就会告诉你(警告warning或者错误error)
               逻辑错误:编译的时候,没有任何语法错误,但是运行的时候产生的现象跟你心中所想的不一致
                              解决问题思路:
                                    第一步:定位出错误的位置(通过打印语句)
                                    第二步:在错误的位置一句句执行代码,把结果手动写出来,对照着看


内核链表  
============
  1. 内核链表:只适用于linux系统(window不要去使用),它是linux中专用的一种链表(本质上就是个双向循环链表)
      优势:前面学习双向循环链表"很痛苦"(画图,理清指针的指向关系,关系理不清容易产生段错误)
               内核链表把我们同学最讨厌的指针操作封装成了函数或者宏定义(方便你直接使用,隐藏了指针操作的细节)
  2. 内核链表的原理:
            普通的双向循环链表定义
               struct  doublelist
               {
                        //数据域
                        //指针域两个指针struct  doublelist   *prev ,  *next
               }
           内核链表的定义
              struct  名字自定义  //把数据和指针做了剥离
             {
                        //数据域
                        struct list_head   mypoint;  //存放了两个指针
            }
             struct  kernellist   //大结构体(宿主结构体)
             {
                    int data;  //数据域
                    struct list_head   mypoint;   //存放指针的结构体,小结构体
             }
  3. 内核链表提供的接口函数或者宏定义
            (1)  初始化指针
                   INIT_LIST_HEAD(ptr)   //宏函数
                            参数:ptr --》你要初始化的那个指针 struct list_head 
            (2)  插入
                  list_add_tail(struct list_head *new, struct list_head *head)
                           参数: 两个小结构体指针
                                   功能一: 把new这个节点插入到head表示的内核链表的尾部   
                                   功能二: 把new这个节点插入到另外一个节点的前面  list_add_tail(new, othernode);

                 void list_add(struct list_head *new, struct list_head *head)  
                           参数:同上
                                   功能一:把new这个节点插入到head和第一个有效节点之间
                                   功能二:把new这个节点插入到另外一个节点的后面  list_add(new, othernode);

           (3)   遍历内核链表
                写法一:
                       list_for_each_entry(pos, head, member)   //宏函数   //用一个指针,pos来遍历
                                 参数:pos --》大结构体指针,用来遍历内核链表
                                          head --》头节点里面的小结构体指针
                                          member --》小结构体在大结构中的名字
                                注意:这种循环适合遍历打印链表中的数据,不适合在遍历的过程中直接删除数据(容易导致段错误)
               写法二:
                       list_for_each_entry_safe(pos, n, head, member)    //用两个指针pos和n一前一后遍历     
                                    参数:pos --》大结构体指针,用来遍历内核链表
                                             n --》大结构体指针,用来遍历内核链表,跟pos一起配合遍历链表
                                             head --》头节点里面的小结构体指针
                                             member --》小结构体在大结构中的名字
                                注意:这种循环,遍历打印,删除节点都没有问题
               写法三:
                      list_for_each(pos, head)
                                    参数:pos --》小结构体指针,用来遍历内核链表
                                             head --》头节点里面的小结构体指针
                      必须配合list_entry(ptr, type, member)一起使用,原因是list_for_each()里面的pos是个小结构体指针(无法访问大结构体中的成员)
                                    作用:通过小结构体得到对应的大结构体(地址转换,源码中通过小结构体指针计算得到大结构体的首地址)
                                    参数:ptr --》小结构指针
                                             type --》大结构体的类型名
                                             member --》小结构体在大结构中的名字
 
          (4)   删除
                void list_del(struct list_head *entry)
                          参数:  小结构体指针
                                   你要删除的那个节点的小结构体指针

4. 总结:
              第一点:  内核链表中有两种转换
                        第一种:大结构调用小结构        struct  kernellist  *p;    p->mypoint       mypoint就是我的小结构体
                        第二种:小结构体得到大结构体  struct  kernellist  *p=list_entry(小结构体指针, 大结构体类型名,小结构体名字) 
             第二点: 
练习:
       1. 熟悉内核链表的接口函数,动手使用内核链表的函数,体会每个参数的含义
       2. 用内核链表存放某个目录种读取的图片的路径,键盘输入next字符串表示下一张(打印下一个节点中的图片名字即可)
                                                                             键盘输入prev字符串表示上一张(打印上一个节点中的图片名字即可)
       3. 扩展: 内核链表还有其它的函数
              //调整节点的位置
              void list_move(struct list_head *list,struct list_head *head)  //把list这个节点挪动到head表示的节点的后面
              void list_move_tail(struct list_head *list,struct list_head *head)  //把list这个节点挪动到head表示的节点的前面    
              void  list_splice(struct list_head *list, struct list_head *head)  //合并链表,把head表示的链表合并到list表示的链表尾部                        
      /usr/src/linux-headers-4.10.0-28/include/linux/list.h


栈和队列
==================
     1.  栈:先进后出
                两种常见的实现方法:  顺序栈(用顺序表实现栈的逻辑)
                                                  链式栈(用单链表实现栈的逻辑)
                (1) 顺序栈
                          栈的初始化
                          压栈(入栈)
                          弹栈(出栈)
                          栈的销毁
                       struct  stack
                       {
                               int  a[10];  //存放整数的顺序栈
                               int  top;  //标记目前栈顶位置
                       };
                      struct  stack  *init_stack()
                      {
                              struct  stack  *mystack=malloc();
                              mystack->top=0;
                              return mystack;
                      }
                      //压栈  push_stack
                      int push_stack(int newdata,struct  stack  *stack)
                     {
                             //判断栈是否满了
                            
                            //newdata入栈
                            stack->a[stack->top]=newdata;
                           //更新栈顶
                           stack->top++;
                    }‘
                    //出栈   pop_stack
                    int pop_stack(struct  stack  *stack)
                    {
                           //判断栈是否空了
                          stack->top--;  
                          //出栈
                          return  stack->a[stack->top];
                    }

          队列:先进先出
  
作业:
     1.  完成顺序栈的代码,三个函数的封装,利用封装的函数实现将键盘输入的任意整数转换成2进制保存到栈里面,然后打印出来  
                                                               用栈实现字符串的逆序输出         


内核链表

#include "myhead.h"
#include "kernel_list.h"

//定义一个结构体表示内核链表
struct kernellist
{
	char bmppath[30]; //真实数据,存放图片的路径名
	struct list_head mypoint;
};

struct kernellist *myhead;

//初始化
struct kernellist *init_list()
{
	struct kernellist *head=malloc(sizeof(struct kernellist));
	strcpy(head->bmppath,"./pic/1.bmp");  //头节点中存放一张图片的路径
	INIT_LIST_HEAD(&(head->mypoint));
	return head;
}

//尾插
int insert_tail(char *newdata,struct kernellist *head)
{
	//定义新的节点
	struct kernellist *newnode=malloc(sizeof(struct kernellist));
	strcpy(newnode->bmppath,newdata);
	INIT_LIST_HEAD(&(newnode->mypoint));
	
	//尾插
	list_add_tail(&(newnode->mypoint),&(myhead->mypoint));
	return 0;
}


//封装递归函数
int myreaddir(char *dirpath)
{
	char filepath[256];
	char subdirpath[256];
	//洋葱剥皮
	DIR *dirp=opendir(dirpath);
	if(dirp==NULL)
	{
		perror("打开目录失败!\n");
		return -1;
	}
	struct dirent *p;
	//循环读取目录
	while((p=readdir(dirp))!=NULL)
	{
		//排除掉.和..  
		if(strcmp(p->d_name,".")==0 || strcmp(p->d_name,"..")==0)
			continue;
		if(p->d_type==DT_REG) //普通文件
		{
			bzero(filepath,256);
			//判断该文件是不是bmp图片
			if(strstr(p->d_name,".bmp")!=NULL)
			{
				//拼接绝对路径打印
				sprintf(filepath,"%s/%s",dirpath,p->d_name);
				//把bmp图片的路径存放到内核链表中
				insert_tail(filepath,myhead);
			}
		}
		if(p->d_type==DT_DIR) //子目录
		{
			bzero(subdirpath,256);
			//拼接绝对路径打印
			sprintf(subdirpath,"%s/%s",dirpath,p->d_name);
			myreaddir(subdirpath); //递归调用自己
		}
	}
}
int main(int argc,char **argv)
{
	char cmd[10];
	//初始化一个内核链表
	myhead=init_list();
	
	//递归读取目录并把bmp图片的绝对路径存放到内核链表中
	myreaddir(argv[1]);
	
	//定义一个小结构体指针指向链表的表头
	struct list_head *q=&(myhead->mypoint);
	struct kernellist *bigq;
	//打印上一张,下一张
	while(1)
	{
		bzero(cmd,10);
		printf("请输入next或者prev分别表示下一张和上一张!\n");
		scanf("%s",cmd);
		if(strcmp(cmd,"next")==0)  //下一张
		{
			//指针往后挪动
			q=q->next;
			//把小结构体转换成对应节点的大结构体
			bigq=list_entry(q,struct kernellist,mypoint); //源码返回的是整个结构体的起始地址
			printf("当前遍历的是:%s\n",bigq->bmppath);
		}
		if(strcmp(cmd,"prev")==0)  //上一张
		{
			//指针往前挪动
			q=q->prev;
			//把小结构体转换成对应节点的大结构体
			bigq=list_entry(q,struct kernellist,mypoint); //源码返回的是整个结构体的起始地址
			printf("当前遍历的是:%s\n",bigq->bmppath);
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值