目录
数据结构
数据结构指:数据的逻辑结构和存储结构及操作 数据:信息的载体 数值型数据:整型、字符型、浮点型等 非数值型数据:图像、视频、声音等 数据项:数据元素由若干个数据项组成,数据项是数据的最小单位
常用的数据结构
逻辑结构: 1、集合:除了同属一个集合,再无其他关系 2、线性结构: 一个对一个,如线性表、栈、队列。数据元素是一对一的关系,每一个节点之间存在前驱和后继的关系,当然除去第一个(没有前驱)和最后一个(没有后继)。 线性结构按照存储类型可以分为:顺序表、顺序栈、顺序队列;链表、链栈、链队列。 3、树状结构:一个对多个,如树。数据元素之间是一对多的关系,每一个元素都只能有一个前驱,可以有多个后继。前驱也叫父节点,后继也叫子节点。 4、图:多个对多个 存储结构:顺序存储、链式存储、索引存储、散列存储
数据结构的基本运算:增、删、改、查
算法的基本概念:
1、程序:一组有序二进制指令、存储在磁盘、可以执行、静态(不会占用系统资源:cpu 内存等) 2、进程:运行起来的程序 3、算法:是一个有穷规则的有序集合
程序与算法的区分
算法特性: 1、有穷性:算法执行步骤有限 2、确定性:每个计算步骤无二义性 3、可行性:每个计算步骤都可以在有限时间内完成 4、输入: 算法有零个或多个外部输入 5、输出: 算法有一个或多个输出 算法和程序既有联系既有不同 相同:二者都是为了完成某个任务,或解决某个问题而编制而成的有序集合。 区别: 1、算法与计算机无关,程序依赖于具体的计算机语言 2、算法必须是又穷尽的,但程序可以是无穷的 3、算法可以忽略一些语法细节,重点在解决问题的思路上,但程序必须严格遵循语言工具的语法。算法必须转换成程序后才能在计算机上运行。
如何判定算法的好坏
1、消耗空间多少 2、消耗时间多少 3、容易理解、编程、调试、维护
时间复杂度
问题的规模 :输入数据量的大小,用n来表示。 算法消耗时间,它是问题规模的函数 T(n)。 时间复杂度: 1. 根据频度写出语句表达式 2. 常数项化为1 3. 保留最高阶,去除其余项 4. 最高阶如果乘数不为1,化为1
顺序表
顺序表本质上是程序员基于结构体构造的一种新的类型。 结构体中以一个数组作为顺序表身,一个下标标识。 特别的: 在顺序表中,typedef int data_t。用datd_t 代表整型。 下标lsat 值为-1表示,表中无数据,last+1 表示数据长度。 数据表特点:内存空间连续存储
顺序表优缺点
优点:查询和修改快速方便 缺点: 1、需要一片连续的空间 2、增加和删除时涉及大量数据移动、耗时 3、当数据表大小确定后不能再次修改。 不适合数据量的时候使用,对表的插入和删除等运算时间复杂度较差。 1、创建顺序表,last = -1 2、判空 3、判满 4、求长度 5、插入 6、删 7、改 8、清空 9、销毁 #define max 16 typedef int data_t; typedef struct data_t{ data_t data[max]; int last; }seqlist; #include<stdio.h> #include<stdlib.h> #include<string.h> #define SIZE 10 typedef int data_t; typedef struct seq{ //构造顺序表类型 data_t data[SIZE]; int last; }seqlist; seqlist* creat_list(); int seqlist_is_empty(seqlist*); int seqlist_is_full(seqlist*); int seqlist_len(seqlist*); int seqlist_initialise(seqlist*,int); int seqlist_outpit(seqlist*); int seqlist_add(seqlist* , int , int); int seqlist_del(seqlist* , int ); data_t find_by_post(seqlist* , int ); data_t * find_by_data(seqlist* , data_t ); int main() { int post,val,i,j; int data =0 ; seqlist* head = creat_list(); //调用函数创建顺序表。 if(NULL == head) { printf("申请空间失败。\n"); exit(-1); }else{ printf("申请空间成功。\n"); } puts("输入赋值节点post <=9 :"); scanf("%d",&post); int ret = seqlist_initialise(head,post); //给顺序表赋值 if(0 == ret ) { printf("赋值成功。\n"); }else { printf("赋值失败。ret = %d\n",ret); exit(-1); } seqlist_outpit(head);//打印 printf("需要插入节点个数:\n"); scanf("%d",&i); while(i--) { printf("输入节点 pos < 9,以及插入数据。用空格隔开。\n"); scanf("%d%d",&post,&val); ret = seqlist_add( head, post, val);//插入节点 if(-1 == ret || -2 == ret || -3 == ret ) { printf("程序出错,退出。ret = %d\n",ret); exit(-1); } seqlist_outpit(head);//打印 } printf("输入需要删除的节点 post\n"); scanf("%d",&post); seqlist_del(head, post);//删除节点 seqlist_outpit(head); puts("输入需要查找的数值,data"); scanf("%d",&data); data_t* postl = find_by_data(head,data); int len_post =postl[0]; for( i = 1 ; i <= len_post ; i++) { printf("data = %d post = %d \n",data,postl[i]); } return 0; } seqlist* creat_list() { seqlist* head = (seqlist*)malloc(sizeof(seqlist)); if(NULL == head) return NULL; memset(head->data,0,SIZE); head->last = -1; return head; } //判空 int seqlist_is_empty(seqlist* head) { if(NULL == head) return -1; return ((head->last == -1)?1:0); } //判满 int seqlist_is_full(seqlist* head) { if(NULL == head) return -1; return ((head->last +1 == SIZE)?1:0); } //求表长(实际元素个数) int seqlist_len(seqlist* head) { if(NULL == head) return -1; return head->last +1; } //赋初值 int seqlist_initialise(seqlist* head, int post) { int i = 0; if(post < 0 || post > (SIZE-1)) return -1; for(i = 0; i <= post ; i++) { head->data[i] = i; } head->last = post; return 0; } //打印当前顺序表 int seqlist_outpit(seqlist* head) { if(NULL == head) return -1; int i = 0; for(i = 0; i <= head->last; i++) { printf("data[%d] = %d ",i,head->data[i]); } puts(""); return 0; } //增 int seqlist_add(seqlist* head, int post, int val) { int i = 0 ; if(NULL == head) return -1; if(1 == seqlist_is_full(head) ) { printf("表已经满,无法插入数据。"); return -2; } if(post < 0 || post > head->last+1) return -3; for(i = head->last ; i >= post ; i--) { head->data[i+1] = head->data[i]; } head->data[post] = val; head->last++; return 0; } //删 int seqlist_del(seqlist* head, int post) { int i ; if(NULL == head ) return -1; if(post < 0 || post > head->last) return -2; if( 1 == seqlist_is_empty(head)) { printf("顺序表空,无法删除"); return -3; } for(i = post ; i < head->last ; i++ ) { head->data[i] = head->data[i+1]; } head->last--; return 0; } //查 data_t find_by_post(seqlist *head , int post) { if(NULL == head) return -1; if(1 == seqlist_is_empty(head)) return -2; if(post < 0 || post > head->last ) return -3; return head->data[post]; }//位置查询 data_t * find_by_data(seqlist* head, data_t data) { int i ,j; if(NULL == head) return NULL; if(1 == seqlist_is_empty(head)) return NULL; j=0; for(i = 0; i < head->last; i++ ) { if(head->data[i] == data) { j++; } } data_t *post = (data_t*)malloc(sizeof(data_t)*j+1); memset(post,0,sizeof(data_t)*j+1); j = 0 ; for(i = 0; i < head->last; i++ ) { if(head->data[i] == data) { post[j+1] = i; j++; } } post[0] = j; return post; }//值查询 //清空表 int clean_seqlist(seqlist* head) { if(NULL == head) return -1; head->last = -1; return 0; } //销毁表 int destroy_seqlist(seqlist** seq) { if(NULL == *seq) return -1; free(*seq); *seq = NULL; }
单链表(不循环)
head->next = NULL 1、创建头节点 2、判空 3、求长度 4、插入:节点插入,头插 5、删 6、改 7、查 8、清空 9、销毁 typedef int data_t; typedef struct data_t{ data_t data; struct data_t* next; }linklist; linklist* head = (linklist*)malloc(sizeof(linklist)); head->data = -1; head->next =NULL;
单向链表(循环)
typedef int data_t; typedef struct data_t{ data_t data; struct data_t* next; }linklist; linklist* head = (linklist*)malloc(sizeof(linklist)); head->data = -1; head->next =head; 与单向不循环链表区别在于:循环链表 head->next = head;
链表的插入
链表插入节点,一般有头插、尾插。 其中头插能够倒置一个链表
双向链表
typedef struct list{ data_t data; struct list* next; struct list* prior; }dlinklist; 在结构上与单向链表区别在于:双向链表的每一个节点有两个结构体指针,分别指向上一个、下一个节点。
顺序栈和链式栈:先进后出
栈:限制在一端进行插入和删除的线性表,允许操作的一端称为“栈顶”,另一固定端称为“栈底”,当栈中没有元素时称为“空栈”。 顺序栈:特殊的顺序表 typedef int data_t typedef strcut { data_t data[max]; int top; }seqstack_t; top =-1; 清空栈 链式栈:头插法
顺序队列和链式队列
顺序队列:
队列:队列是限制在两端进行插入和删除操作的线性表,允许进行插入操作的一端称为“队尾“,允许进行删除操作的一端称为”队头“,当线性表中没有元素时,称为”空队“。 顺序队列:特殊的顺序表 typedef int date_t; typedef struct { data_t data[max]; int front; int rear; }sequeue_t; front :头下标(头元素); rear:尾下标(尾元素下一个); 判断空:front = rear (空) 判断满:front = (rear+1)%size 长度: (rear - front+siez)%size 入队: head->data[head->real] = val; head->real = (head->real+1)%SIZE; 出队: data_t val = head->data[head->front] ; head->front = (head->front+1)%SIZE; #include <stdio.h> #include<stdlib.h> #define MAX 80 #include<string.h> typedef int data_t; typedef struct data{ data_t data[MAX]; data_t front;//指向头 data_t rear;//指向尾 }sequeue; sequeue* create_seq();//创建顺序队列 int seqlist_is_empty(sequeue * seqlist);//判空 int seqlist_is_full(sequeue* seqlist);// 判满 int add_data_into_list(sequeue*seqlist ,int data);//加入队列 int del_data_list(sequeue* seqlist);//输出队列 int main(int argc, char *argv[]) { sequeue* seqlist = create_seq(); /*int i =0,num ; for(i = 0; i< 5;i++) { num = add_data_into_list(seqlist,i); } for(i =0 ;i< 5 ;i++) { printf("%d\n",del_data_list(seqlist)); }*/ tree_node* tree = create_tree();//创建二叉树 level_print(tree,seqlist);//层次遍历二叉树 return 0; } sequeue* create_seq()//创建顺序队列 { sequeue* seqlist = (sequeue*)malloc(sizeof(sequeue)); if(NULL == seqlist) return NULL; memset(seqlist->data,0,sizeof(seqlist->data)); seqlist->front = 0; seqlist->rear = 0; return seqlist; } int seqlist_is_empty(sequeue * seqlist)//判空 { if(NULL == seqlist) return -1; // printf("seq->front = %d , reer = %d\n",seqlist->front,seqlist->rear); return ((seqlist->front == seqlist->rear)?1:0); } int seqlist_is_full(sequeue* seqlist)// 判满 { if(NULL == seqlist) return -1; return (((seqlist->rear+1)%MAX == seqlist->front)?1:0); } int add_data_into_list(sequeue*seqlist ,int data)//加入队列 { if(NULL == seqlist) return -1; if(1 == seqlist_is_full(seqlist)) return -2; seqlist->data[seqlist->rear] = data; seqlist->rear = (seqlist->rear+1)%MAX; return 0; } int del_data_list(sequeue* seqlist)//输出队列 { if(NULL == seqlist) return -1; if(1 == seqlist_is_empty(seqlist)) return -1; int val = seqlist->data[seqlist->front]; seqlist->front = (seqlist->front+1)%MAX; return val; }
链式队列:
typedef int data_t; typedef struct node{ data_t data; struct node* next; }linklist; //链表节点类型 typedef struct{ linklist* front, *rear; //和链表节点类型一致 }lqueue; //构造链式队列结构 lqueue *create_lqueue() { lqueue *lq = (lqueue*)malloc(sizeof(lqueue)); //创建链式队列头节点指针 if(NULL == lq) return NULL; // linklist *front = (linklist*) malloc(sizeof(linklist)); 如果没有构造链式队列 应该这样创建两个指针 lq->front = (linklist*) malloc(sizeof(linklist)); //给链表头节点 空间并初始化 lq->rear = lq->front; lq->rear->data = -1; lq->rear->next = NULL; return lq; } //判空 int lqueue_is_empty(lqueue* lq) { if(NULL ==lq) return -1; return((lq->front->next== lq->rear->next )?1:0); } //求链式队列长度 int get_length_lqueue(lqueue* lq) { if(NULL == lq) return -1; int num = 0; linklist* p=lq->front->next; while(p!=NULL) { num++; p=p->next; } return num; } //入队 int enlqueue(lqueue* lq,data_t vlu) { if(NULL == lq) return -1; linklist *new = (linklist* )malloc(sizeof(linklist)); new->data = vlu; new->next = NULL; lq->rear->next = new; //把新节点接如链表 lq->rear = new; //尾指针往后偏移到新的尾节点 } //出队 int delqueue(lqueue* lq) { if(NULL == lq) return -1; if(lqueue_is_empty(lq) == 1) return -1; data_t data = lq->front->next->data; // 第一个节点的数据 linklist *p = lq->front->next; // 备份 第一个节点地址 lq->front->next = p->next ; // 重新连接第二个节点到队头 free(p); return data; } int main(int argc,const char *argv[]) { lqueue *lq = create_lqueue(); if(NULL == lq) return -1; enlqueue(lq, 11); enlqueue(lq, 22); enlqueue(lq, 33); printf("%d\n", delqueue(lq)); printf("%d\n", delqueue(lq)); printf("%d\n", delqueue(lq)); printf("%d\n", delqueue(lq)); return 0; }
树
概念:是一种非线性结构,数据之间的关系“一对多”,节点只能有一个前驱,可以没有或多个后继。
树的性质:
1、根节点:树的起始节点,没有前驱节点。 2、叶节点:没有后继节点,也被称为终端节点。 3、父节点与子节点:节点的直接后继节点称为该节点的子节点,该节点称为子节点的父节点。 4、树的层数:从根节点开始,从1开始依次往下计数 5、树的高度:最后一层的层数为该数的高度 6、树的度数:节点的子节点个数就是该节点的度数,树的度数是树中最大节点度数
二叉树
定义:树中每个节点度数不能大于2
二叉树性质:
1、第i层最多2^(i-1) 个节点 2、高度为n的树,最多(2^n)-1个节点 3、任意二叉树中,树叶的个数比度数为2 的节点多一个。 n0 :表示叶节点个数 n1:表示度数为1的节点个数 n2:表示度数为2的节点数 任意二叉树有: n0 = n2 +1
二叉树的存储方式:
顺序存储或链式存储
链式存储:
//利用递归思想创建一个任意形态的二叉树(具体形态取决于输入的情况:输入-1 代表无子节点) typedef int datatype; typedef struct node{ datatype data; struct node* left, *right; }tree_node; tree_node* create_tree() { int num = 0; tree_node* p = NULL; puts("输入节点数据:"); scanf("%d",&num); if(num == -1) { return NULL; }else{ p = (tree_node*)malloc(sizeof(tree_node)); if( NULL == p ) return NULL; p->data = num; p->left = create_tree(); p->right = create_tree(); } return p; }
顺序存储:
非完全二叉树使用顺序存储,可能会浪费空间。
遍历方式:
先序遍历:(递归)
先遍历根节点,再遍历左子树,最后右子树。 int look_node_xianxu(tree_node* tree) { if(NULL == tree) return -1; printf("%d\n",tree->data); look_node_xianxu(tree->left); look_node_xianxu(tree->right); return 0; }
中序遍历(递归)
中序遍历:先遍历左子树,再遍历根节点,最后右子树。 int look_node_houxu(tree_node *tree) { if( NULL == tree) return -1; look_node_houxu(tree->left); printf("%d\n",tree->data); look_node_houxu(tree->right); return 0; }
后序遍历(递归)
后序遍历:先遍历左子树,再遍历右子树,最后根节点。 int look_node_houxu(tree_node *tree) { if( NULL == tree) return -1; look_node_houxu(tree->left); look_node_houxu(tree->right); printf("%d\n",tree->data); return 0; }
层次遍历
从第一层开始依次往下,从左往右遍历。(借助队列遍历) int cengci_bianli(tree_node *tree) { int num = 0; tree_node* data[MAX]; int front = 0; int rear = 0; data[rear++] = tree; while(rear != front) { if(data[front]->left != NULL ) { data[rear++] = data[front]->left; } if(data[front]->right != NULL) { data[rear++] = data[front]->right; } num = data[front++]->data; printf("%d ",num); } }
已知先序 与中序 求后序( 先序第一个为头节点,中序头节点前为树左半边)。
已知后序与 中序 求先序(后序最后一个节点为头节点)。
特殊二叉数列
满二叉树:每一层都是2^(i-1)个节点
完全二叉树:
1、只有最下两层有度数小于2的节点 2、最下面一层叶节点集中于最左侧 3、具有n个节点的完全二叉树的深度: ((向下取整:(log2 N))+1) 或 向上取整(log2 (n+1)) tree_node* create_list(int i, int num)//完全二叉树 { tree_node* tree = (tree_node*)malloc(sizeof(tree_node)); if(NULL == tree) return NULL; tree->data = i; if(2*i < num) { tree->left = crete_tree(2*i,num); }else{ tree->left = NULL; } if(2*i+1 < num) { tree->right= crete_tree(2*i+1,num); }else{ tree->right = NULL; } return tree; }
二叉查找树
又称:二叉排序树 中序遍历,数据为升序排列 每一颗子树左节点数据需要小于根节点,右节点数据大于根节点。 优点:查找效率高 缺点:容易成为链表
平衡二叉树:
1、一颗二叉查找树 2、每个节点的左右子树的高度差不能大于1(从下往上算)。 平衡因子就是每个节点的左右子树的高度差,可以检测某个节点是否平衡。 优点:查找效率快 缺点:插入数据,涉及大量数据移动,效率低 该树要求:“绝对平衡”
红黑树
该树要求:适度平衡(局部平衡)。 1、根节点为黑色 2、个节点不是黑色就是红色 3、每个叶子节点都是黑色 4、一个节点是红色,其子节点必须是黑色 5、从一个节点到该叶子节点的所有路径上包含相同数目的黑点。 插入方式: 1、染色 2、旋转 优点:查找效率快 优点:插入数据时,只要求“适度平衡”,因此不会每次去旋转大量节点。
总结
平衡二叉树(AVL树)与红黑树比较: 平衡二叉树要求“绝对平衡”,所以插入时效率慢,但是查找效率快 红黑树只要求适度平衡,所以不需要每次旋转,插入效率快。红黑树最长的路径不能大于最短路径的两倍,所以查找效率较快。
最优二叉树
又称:赫夫曼树 WPL最小的二叉树就是最优二叉树,又称为赫夫曼树。 树的带权路径长度指所有叶子节点的带权路径之和。
构造最优二叉树
按照叶节点的权值构造最优二叉树 1. 先对权值进行排序----> 2, 3, 5, 7, 8 2. 选择权值最小的两个节点,可以规定权值较小的节点作为左节点, 大的作为右节点. 3. 构造出新的权值节点,然后将新的权值重新进行排序(以前使用过的权值不在参与排序). 重复1,2,3直到完成树的构造。
图
逻辑关系:非线性,数据间多对多。 图的性质: 顶点:每一个数据元素被称为顶点 图分为:有向图、无向图。 有向图:把两个顶点之间的连线称为:弧 无向图:把两个顶点之间的连线称为:边 顶点的度数: 无向图:与该节点直接相连的节点数。 有向图:出度+入度 存储结构: 1、顺序存储 利用数组来存放图的顶点数据,利用邻接矩阵(二维数组)表达顶点之间的关系。 2、链式存储(邻接表)
图的遍历方式
图的存储方式
可以用一个一维数组存储图的节点,再用一个二维数组存储节点之间的关系。 #define SIZE 20 typedef int datatype; typedef struct ma{ datatype v[SIZE]; //一维数组用于存储节点数据,(根据节点在一维数组中的下标,标定两个有关系的节点。方便存储和遍历) int matrix[SIZE][SIZE];//二维数组存储 }map_t; map_t* create_tu()//根据已知的图的关系输入计算机。让计算机存储和查找 { int num,i=0,j; map_t* map = (map_t*)malloc(sizeof(map_t)); if(NULL == map) return NULL; memset(map,0,sizeof(map)); printf("输入顶点:(-1 退出)\n"); while(1)//按照顺序输入节点 { scanf("%d",&num); getchar(); if(num == -1 || i >= SIZE) break; map->v[i++] = num; } printf("输入两数据间的关系,数据间用逗号隔开:i,j \n"); while( scanf("%d,%d",&i,&j) == 2)//输入有关系的节点,比如在一维数组中,下标为1和2的节点有关系,就可以让a[1][2]和a[2][1]都赋值为1 { getchar(); if(i < 0 || j < 0 || i >= SIZE || j >= SIZE) break; map->matrix[i][j] =1; map->matrix[j][i] =1; } return map; }
深度优先 DFS
类似树的先序遍历(递归) 如上图如果从v1开始遍历,深度优先顺序如下 v1->v2->v3->v4->v3->v5 从v1开始,与他相连的节点有两个v2 v4,深度优先的原则是先遍历下标小的节点v2(下标的大小是由输入一维数组顺序决定,也就是由程序源决定) 接着与v2相连的节点有:1,3,5;其中1已经遍历,所以此次遍历3 与3相连的有 4 ,5;此次遍历4 与4相连的有1,3,两个都遍历过了,所以应该回到节点3(已经遍历过了) 与3相连的有4,5;4遍历了,所以继续遍历5. 最终遍历顺序为: v1->v2->v3->v4->v5 int fistadj(map_t* map, int pos) { int i = 0; for(i =0 ; i <SIZE ;i++) { if(1 == map->matrix[pos][i]) return i; } return -1; } int nextAdj(map_t* map,int pos, int u) { int i = 0; for(i = u+1 ;i < SIZE; i++) { if(1 == map->matrix[pos][i]) return i; } return -1; } void DFS(map_t* map, int pos, int visited[])//遍历函数,调用时可以输入pos指定节点开始。 { int u; printf("%-4d",map->v[pos]);//打印当前节点数据 visited[pos] =1;//另外定义并初始化为零的数组,大小与存储节点的数组一样大,每个节点被遍历,该数组对应下标元素赋值为 1 u = fistadj(map,pos);//调用函数找到与当前节点相连节点中,下标最小的节点。 while( u != -1) { if(visited[u] == 0)//节点没有被遍历,调用函数继续遍历 DFS(map,u,visited); u = nextAdj(map,pos,u+1);//当前节点已经遍历,找其他节点 } return ; }
广度优先
类似树的层次遍历(队列)
排序
快速排序
int quick_sort(int a[], int low, int high) { int i = low; int j = high; int temp = a[low]; if(i < j) { while(i < j) { while(i < j && temp <= a[j]) j--; if(i < j) a[i] = a[j]; while(i < j && temp >= a[i]) i++; if(i < j) a[j] = a[i]; } a[i] = temp; quick_sort(a, low, i-1); quick_sort(a, i+1, high); }
非递归方式遍历二叉树:
先序:
typedef int data_t; typedef struct node{ data_t data; struct node* left; struct node* right; }tree_node; int traverse_pre_tree(tree_node*tree)//先序 { if(NULL == tree)//根节点为空函数退出 return -1; tree_node* data[50];//创建一个栈(指针数组),储存节点地址. int last = -1; tree_node* p = tree; data[++last] = tree;//根节点入栈 while(last >-1)//栈空,循环结束 { p = data[last];//p指向栈顶元素 printf("%2d ",data[last--]->data);//栈顶元素出栈 if(p->right != NULL) { data[++last] = p->right;//右节点入栈. } if(p->left != NULL) { data[++last] = p->left;//左节点入栈 } } }
中序
int traverse_post_tree(tree_node*tree)//中序 { if(NULL == tree)//根节点为空函数退出 return -1; tree_node* data[50];//创建一个栈(指针数组),储存节点地址. tree_node* p =tree; int last = -1; do{ while(p != NULL) { data[++last] =p;//根节点入栈 p = p->left;//所有左节点入栈 } if(last != -1) { p = data[last];//指针指向栈顶元素 printf("%2d ",data[last--]->data);//栈顶元素出栈 p = p->right;//指针指向栈顶元素右节点 } }while(p != NULL || last != -1); }