本文作者: HuaShan
本文链接: http://denghs.com/2018/02/26/data_struct/
之前在写Java中的容器时,有写到一些关于数据结构的部分,可以参考《Java的List、Set、Map容器》一文。这里再详细回顾一下对数据结构的学习。
字节:存储数据的单元,是硬件所能访问的最小单位。
1字节=8位,1k=1024字节,1M=1024k,1G=1024M。对于4G内存的电脑来说,所能存放数据最多是(4*1024*1024*1024*8)位的二进制数据。
物理内存地址:内存单元的编号,从零开始的非负整数
指针:指针就是地址,地址就是指针。
指针变量:存放内存单元编号的变量,就是存放地址的变量。
结构体:把一些基本类型数据组合在一起,形成一个新的复合数据类型。
一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占4个字节。用第一个字节的地址表示整个变量的地址。一个变量的地址是用该变量首字节的地址来表示。CPU通过地址总线、控制总线、数据总线,俗称三根总线,来操作物理内存。
package datastructure.part.one;
import java.util.Arrays;
/**
* @description
* @author Denghs
* @version 1.0,2017年11月23日 下午2:43:18
* @remark
*/
public class TestOn {
public static void main(String[] args) {
long n = 10000;
method1(n);
method2(n);
long start = System.currentTimeMillis();
long sum = method3(n);
System.out.println("method3:" + (System.currentTimeMillis() - start) + "毫秒,sum=" + sum);
}
/**
* 用for循环进行1+2+3+...+100的计算。 该方法的时间复杂度是O(n)
* @param n
*/
public static void method1(long n) {
long start = System.currentTimeMillis();
long sum = 0;
for (long i = 1; i <= n; i++) {
sum += i;
}
System.out.println("method1: " + (System.currentTimeMillis() - start) + "毫秒,sum=" + sum);
}
/**
* 用高斯公式进行1+2+3+...+100的计算。 该方法的时间复杂度是O(1)
* @param n
*/
public static void method2(long n) {
long start = System.currentTimeMillis();
long sum = (n + 1) * n / 2;
System.out.println("method2: " + (System.currentTimeMillis() - start) + "毫秒,sum=" + sum);
}
/**
* 用递归的方式计算1+2+3+...+100的和。数值过大,会导致内存溢出,递归本质是函数的不停的压栈、出栈的操作
* @param n
*/
public static long method3(long n) {
if(n == 1){
return 1;
}else{
return n + method3(n-1);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-icbcFmu2-1591368612809)(http://orxmumro7.bkt.clouddn.com/18-4-18/73556896.jpg)]
从上图的运行结果可以明显的看到,当问题计算的规模n=10000时,好像method1、method2、method3彼此的计算耗时都是0毫秒。但是当n再扩大10倍时,method1花了2毫秒,method3直接抛异常了,而method2而然是0毫秒。
method1是用for循环的方式,method2则是高斯公式,method3是用递归的方式。
在数据结构领域,关于算法的时间复杂度,有一个计算公式,大O表示法。这里不做过多说明,教科书上有该公式的计算方法跟定论。
数据结构中关于数据的逻辑结构,只分为线性结构、非线程结构。所谓的线性结构,我们可以用一个生活化的场景来理解它,用穿好线的针能够一次把元素都串起来。线性结构是指元素只能有一个顶点、一个端点,中间的元素只有一个前驱跟一个后继。
可以把串在一起的元素,紧紧的挤压在一个方向。让元素紧挨着元素。这就可以理解成是一个顺序表。顺序表其实就是数组,一次给一片连续的空间,然后把空间按一个一个的小格子划分好。其特点是:当CPU在内存中找不到连续一片的符合要求的空间,会导致分配失败。可以通过存放元素格子的宽度,也就是偏移量乘以第几个元素,获取指定地址空间上的元素。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fLqPqNx5-1591368612811)(http://orxmumro7.bkt.clouddn.com/18-4-18/79473786.jpg)]
#include
#include
#include
//连续存储数组的算法
struct Arr
{
int * pBase; //存储容器中第一个元素的地址
int capacity; //所能容纳元素的最大容量
int length; //当前有效元素的个数
};
void init_arr(struct Arr *, int capacity);//初始化容器
bool append_arr(struct Arr *, int value);//追加元素
bool insert_arr(struct Arr *, int index, int value);//在指定的角标处插入元素
bool delete_arr(struct Arr *, int index, int * delete_value);//删除指定角标处的元素,并返回被删除的元素的值
int get(struct Arr *, int index);//获取指定角标的元素
bool is_empty(struct Arr *);//容器是否空
bool is_full(struct Arr *);//容器是否已满
void sort_arr(struct Arr *);//按元素的值自然排序
void print_arr(struct Arr *);//输出元素的值
void invert_arr(struct Arr *);//元素值前后倒置
void main()
{
int delete_value;
int get_value;
struct Arr array;
init_arr(&array, 10);
print_arr(&array);
printf("\n-------分隔符-追加-----\n");
append_arr(&array, 10);
append_arr(&array, 6);
append_arr(&array, 2);
append_arr(&array, -1);
append_arr(&array, 9);
print_arr(&array);
printf("\n-------分隔符-插入-----\n");
insert_arr(&array, 0, 7);
//insert_arr(&array, 6, -18);
//insert_arr(&array, 1, 72);
print_arr(&array);
/**printf("\n-------分隔符-删除-----\n");
bool flag = delete_arr(&array, 5, &delete_value);
print_arr(&array);
if(flag){
printf("被删除的元素的值:%d\n", delete_value);
}
printf("\n-------分隔符-获取-----\n");
get_value = get(&array, 5);
printf("角标 %d 的元素值:%d\n", 5 ,get_value);
get_value = get(&array, 0);
printf("角标 %d 的元素值:%d\n", 0 ,get_value);
printf("\n-------分隔符-倒置-----\n");
invert_arr(&array);
print_arr(&array);
printf("\n-------分隔符-排序-----\n");
sort_arr(&array);
print_arr(&array);*/
}
//初始化容器
void init_arr(struct Arr * pArr, int capacity)
{
pArr->pBase = (int *)malloc(sizeof(int) * capacity);//动态分配内存
if(NULL == pArr->pBase){
printf("动态分配内存失败!\n");
exit(-1);//程序退出。跟Java中的System.exit(-1);一样
}
pArr->capacity = capacity;
pArr->length = 0;//未存储元素,有效元素为0个
}
//输出元素的值
void print_arr(struct Arr * pArr){
printf("容器大小为:%d,有效元素个数为:%d\n", pArr->capacity, pArr->length);
if(is_empty(pArr)){
printf("空容器!\n");
}else{
for(int i=0; i<pArr->length; i++){
printf("%d ", pArr->pBase[i]);
}
printf("\n");
}
}
//容器是否空
bool is_empty(struct Arr * pArr){
//有效元素的个数为0
return pArr->length == 0 ? true:false;
}
//容器是否已满
bool is_full(struct Arr * pArr){
//有效元素的个数=容器的长度
return pArr->length == pArr->capacity ? true:false;
}
//追加
bool append_arr(struct Arr * pArr, int value){
//如果容器满了
if(is_full(pArr)){
return false;
}
//在当前容器的最后一个位置处追加元素
pArr->pBase[pArr->length] = value;
pArr->length++;
return true;
}
//在指定的角标处插入元素
bool insert_arr(struct Arr * pArr, int index, int value){
int i = 0;
//如果容器满了
if(is_full(pArr)){
printf("容器已满!\n");
return false;
}
//index的值不能是负数,不是超过容器存储的有效元素个数
//index=有效元素个数时,表示在最后一个元素处追加一个元素值
if(index < 0 || index > pArr->length){
return false;
}
//先移位,把指定角标位及该角标之后存放的元素,往后挪一个位子存放
for(i = pArr->length - 1; i >= index; i--){
pArr->pBase[i+1] = pArr->pBase[i];
}
//把值存入指定角标处
pArr->pBase[index] = value;
pArr->length++;
return true;
}
//删除指定角标处的元素,并返回被删除的元素的值
bool delete_arr(struct Arr * pArr, int index, int * delete_value)
{
int i;
//如果容器满了
if(is_empty(pArr)){
printf("容器已空!\n");
return false;
}
//index的值不能是负数,不是超过容器存储的有效元素个数
if(index < 0 || index > pArr->length-1){
return false;
}
//把要删除的指定角标处的值取出来
*delete_value = pArr->pBase[index];
//需要移位,要删除的指定角标之后的值需要往前移动一位
for(i = index + 1; i < pArr->length; i++){
pArr->pBase[i-1] = pArr->pBase[i];
}
pArr->length--;
return true;
}
//获取指定角标位置的元素值
int get(struct Arr * pArr, int index){
//index的值不能是负数,不是超过容器存储的有效元素个数
if(index < 0 || index > pArr->length-1){
//应该是Java中的数组角标越界异常,不能确定此处返回的值是否是一个有效的值
return -1;
}
return pArr->pBase[index];
}
//元素值前后倒置
void invert_arr(struct Arr * pArr){
int start = 0;
int end = pArr->length-1;
int temp;
while(start < end){
temp = pArr->pBase[start];
pArr->pBase[start] = pArr->pBase[end];
pArr->pBase[end] = temp;
start++;
end--;
}
}
//按元素的值自然排序
void sort_arr(struct Arr * pArr){
int i, j, temp;
for(i = 0; i<pArr->length; i++){
for(j=i+1; j<pArr->length; j++){
if(pArr->pBase[i] > pArr->pBase[j]){
temp = pArr->pBase[j];
pArr->pBase[j] = pArr->pBase[i];
pArr->pBase[i] = temp;
}
}
}
}
可以把串在一起的元素,中间的线还流出一点点来。只要顺着线能找到元素。这就可以理解成是一个链式结构。**每一个数据元素的地址可以是分散开来的,动态分配,彼此通过指针相连。每个结点只有一个后续结点,首结点没有前驱,尾结点没有后续。**链式结构,也就是俗称的链表。对于链表的操作,额外增加一个头结点,以方便对整个链表的操作。头结点跟头指针是两个不同的概念。头结点的数据域部分是空的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9fK84EZ-1591368612814)(http://orxmumro7.bkt.clouddn.com/18-4-18/63551606.jpg)]
在单链表的结构中,数据元素要有两部分,一部分存放数据元素的数据部分,一部分存放下一个数据元素的地址。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yITX2rMg-1591368612816)(http://orxmumro7.bkt.clouddn.com/18-4-18/66911155.jpg)]
#include
#include
#include
typedef struct Node{
int data;//数据域
struct Node * pNext;//指针域
}NODE, *PNODE;
//创建链表
PNODE creat_list();
//输出链表
void print_list(PNODE);
//是否为空链表
bool is_empty(PNODE);
//获取链表的长度
int get_len(PNODE);
//对链表进行排序:按自然顺序
void sort_list(PNODE);
//插入结点
bool insert_node(PNODE,int,int);
//删除结点
bool delete_node(PNODE,int,int*);
void main(){
int len;
int deleteVal;
PNODE pHead = NULL;
pHead = creat_list();
//输出值
print_list(pHead);
if(is_empty(pHead)){
printf("空链表\n");
}else{
printf("链表不为空\n");
}
//获取链表长度
len = get_len(pHead);
printf("链表的长度为:%d\n", len);
//排序
sort_list(pHead);
print_list(pHead);
//插入
if(insert_node(pHead, 1, 33)){
printf("插入成功!\n");
}else{
printf("插入失败!\n");
}
print_list(pHead);
//删除
if(delete_node(pHead, 0, &deleteVal)){
printf("删除成功!\n");
}else{
printf("删除失败!\n");
}
print_list(pHead);
}
//创建链表
PNODE creat_list(){
int len;
int value;
int i;
//创建头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(NULL == pHead){
//创建失败
printf("分配内存失败,程序终止!\n");
exit(-1);
}
printf("请输出链表的结点个数:");
scanf("%d", &len);
PNODE pTail = pHead;
pTail->pNext = NULL;
//循环生成链表的每个节点
for(i=0; i<len; i++){
printf("请输出第%d个结点的值:", i+1);
scanf("%d", &value);
//创建一个新的结点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
pNew->data = value;
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
//输出链表
void print_list(PNODE pHead){
PNODE p = pHead->pNext;
while(NULL != p){
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
}
//是否为空链表
bool is_empty(PNODE pHead){
return NULL == pHead->pNext ? true:false;
}
//获取链表的长度
int get_len(PNODE pHead){
int len = 0;
PNODE p = pHead->pNext;
while(NULL != p){
p = p->pNext;
len++;
}
return len;
}
//对链表进行排序:按自然顺序
void sort_list(PNODE pHead){
int i, j, temp;
//总长度
int len = get_len(pHead);
//一个当前节点,一个当前节点的下一个节点
PNODE pNow, pNext;
for(i=0, pNow=pHead->pNext;i<len-1;i++, pNow = pNow->pNext){
for(j=i+1, pNext = pNow->pNext;j<len;j++, pNext = pNext->pNext){
if(pNow->data > pNext->data){ //a[i] > a[j]
temp = pNow->data; //temp = a[i]
pNow->data = pNext->data; //a[i] = a[j]
pNext->data = temp; //a[j] = temp;
}
}
}
}
//在指定位置插入结点,position从0开始
bool insert_node(PNODE pHead, int position, int value){
//获取链表的总长度
//int len = get_len(pHead);
//判断position是否是一个有效数。长度是5,position为6,表示是在末尾处添加。index为7,超过有效值
//if(position > len + 1){
//return false;
//}
int len = 0;
//直接定位到需要插入结点的前一结点
PNODE p = pHead;
while(NULL != p && len < position){
p = p->pNext;
len++;
}
//结点不存在
if(len > position || NULL == p){
return false;
}
//创建一个新结点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew){
printf("动态内存分配失败!程序退出\n");
exit(-1);
}
pNew->data = value;
pNew->pNext = p->pNext;
p->pNext = pNew;
return true;
}
//删除指定位置的结点,position从0开始
bool delete_node(PNODE pHead, int position ,int* deleteVal){
int len = 0;
//直接定位到需要插入结点的前一结点
PNODE p = pHead;
while(NULL != p->pNext && len < position){
p = p->pNext;
len++;
}
if(len > position || NULL == p->pNext){
return false;
}
//要删除的结点
PNODE q = p->pNext;
*deleteVal = q->data;
//把p的指针域指向要删除结点的下一个节点
p->pNext = p->pNext->pNext;
//释放删除结点的内存空间
free(q);
q = NULL;
return true;
}
在双链表的结构中,数据元素要有三部分,一部分存放数据元素的数据部分,一部分存放下一个数据元素的地址,一部分存放上一个数据元素的地址。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pDARZRzD-1591368612818)(http://orxmumro7.bkt.clouddn.com/18-4-18/12872314.jpg)]
操作受限的线性表,也称先进先出表。队尾插入,队头取出元素。在非空队列中,队头指针始终指向队头元素,队尾指针始终指向队尾元素的下一个位置。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bSLdHyLz-1591368612820)(http://orxmumro7.bkt.clouddn.com/18-4-18/99260764.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZxfKJtu0-1591368612821)(http://orxmumro7.bkt.clouddn.com/18-4-19/94670713.jpg)]
#include
#include
//循环队列,先进先出
typedef struct Queue{
int * pBase;//动态数组
int front;//队列中有效元素的头元素
int rear;//队列中有效元素的尾元素
}QUEUE;
//初始化循环队列
void init(QUEUE *);
//入队,在队列有效元素的尾部添加
bool add(QUEUE *, int);
//出队,从队头开始获取
bool get(QUEUE *, int *);
//是否已经满
bool isFull(QUEUE *);
//是否已经空
bool isEmpty(QUEUE *);
//打印元素
void print(QUEUE *);
void main(){
QUEUE q;
int delValue;
init(&q);
add(&q, 1);
add(&q, 2);
add(&q, 3);
add(&q, 4);
add(&q, 5);
add(&q, 6);
add(&q, 7);
add(&q, 8);
print(&q);
get(&q, &delValue);
printf("1出队的值为:%d\n", delValue);
get(&q, &delValue);
printf("2出队的值为:%d\n", delValue);
get(&q, &delValue);
printf("3出队的值为:%d\n", delValue);
get(&q, &delValue);
printf("4出队的值为:%d\n", delValue);
get(&q, &delValue);
printf("5出队的值为:%d\n", delValue);
get(&q, &delValue);
printf("6出队的值为:%d\n", delValue);
add(&q, 8);
add(&q, 9);
get(&q, &delValue);
printf("7出队的值为:%d\n", delValue);
}
//初始化循环队列
void init(QUEUE * pQueue){
pQueue->pBase = (int *) malloc(sizeof(int) * 6);
pQueue->front = 0;
pQueue->rear = 0;
}
//入队,在队列有效元素的尾部添加
bool add(QUEUE * pQueue, int value){
if(isFull(pQueue)){
printf("队列已经放满了!\n");
return false;
}else{
pQueue->pBase[pQueue->rear] = value;
pQueue->rear = (pQueue->rear + 1) % 6;
return true;
}
}
//出队,从队头开始获取
bool get(QUEUE * pQueue, int * delValue){
if(isEmpty(pQueue)){
printf("队列已经空了!\n");
return false;
}else{
*delValue = pQueue->pBase[pQueue->front];
pQueue->front = (pQueue->front + 1) % 6;
return true;
}
}
//是否已经满
bool isFull(QUEUE * pQueue){
if((pQueue->rear+1) % 6 == pQueue->front){
return true;
}
return false;
}
//是否已经空
bool isEmpty(QUEUE * pQueue){
if(pQueue->rear == pQueue->front){
return true;
}
return false;
}
//打印元素
void print(QUEUE * pQueue){
int front = pQueue->front;
//队头数不等于队尾数,则表示一直都有元素
while(front != pQueue->rear){
printf("%d ", pQueue->pBase[front]);
front = (front+1) % 6;
}
printf("\n");
}
栈是限定在表的一端进行插入和删除运算的线性表,通常将插入、删除的一端称为栈顶。链式栈,将对链表的操作进行一定的限制,在一端进行插入、删除即可。链式栈,适合开口向上或向下的操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YUXrNhcb-1591368612822)(http://orxmumro7.bkt.clouddn.com/18-4-19/13995764.jpg)]
栈的四种不同操作方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Y45XZNp-1591368612823)(http://orxmumro7.bkt.clouddn.com/18-4-19/55282453.jpg)]
#include
#include
#include
//链表的节点数据对象
typedef struct Node{
int data;//数据域
struct Node * pNext;//指针域
}NODE, * PNODE;
//栈-->类似箱子,先进后出
typedef struct Stack{
PNODE pTop;
PNODE pBottom;
}STACK, * PSTACK;
//初始化栈
void init(PSTACK);
//压栈
bool push(PSTACK, int);
//输出栈中的数据
void print(PSTACK);
//出栈
bool pop(PSTACK, int*);
//清空
void clear(PSTACK);
//是否是空栈
bool isEmpty(PSTACK);
void main(){
STACK stack;
int value;
int i = 0;
init(&stack);
push(&stack, 1);
push(&stack, 2);
push(&stack, 3);
push(&stack, 4);
push(&stack, 5);
push(&stack, 6);
print(&stack);
clear(&stack);
if(pop(&stack, &value)){
printf("出栈成功!元素值为:%d\n", value);
}else{
printf("出栈失败!");
}
print(&stack);
}
//初始化栈
void init(PSTACK pStack){
pStack->pTop = (PNODE)malloc(sizeof(NODE));
if(NULL == pStack->pTop){
printf("分配内存失败!\n");
exit(-1);
}else{
pStack->pBottom = pStack->pTop;
pStack->pTop->data = NULL;
}
}
//压栈
bool push(PSTACK pStack, int value){
//先造一个节点出来
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew){
printf("分配内存失败!\n");
return false;
}else{
pNew->data = value;
pNew->pNext = pStack->pTop;
pStack->pTop = pNew;
return true;
}
}
//输出栈中的数据
void print(PSTACK pStack){
PNODE node = pStack->pTop;
while(node != pStack->pBottom){
printf("%d ", node->data);
node = node->pNext;
}
printf("\n");
}
//出栈
bool pop(PSTACK pStack, int * value){
if(isEmpty(pStack)){
printf("已经是空栈了\n");
return false;
}else{
//要出栈的节点
PNODE node = pStack->pTop;
//出栈节点的值
*value = node->data;
//pTop往下移一个
pStack->pTop = node->pNext;
//释放出栈节点的内存
free(node);
return true;
}
}
//是否是空栈
bool isEmpty(PSTACK pStack){
return pStack->pTop == pStack->pBottom ? true : false;
}
//清空
void clear(PSTACK pStack){
PNODE p = pStack->pTop;
PNODE q = NULL;
while(p != pStack->pBottom){
q = p->pNext;
//释放内存
free(p);
p = q;
}
pStack->pTop = pStack->pBottom;
}
顺序表栈,适合开口向左或向右的操作。顺序存储结构,采用数组实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZGZR8pl-1591368612824)(http://orxmumro7.bkt.clouddn.com/18-4-19/51749974.jpg)]
package datastructure.part.one;
/**
* @description
* @author Denghs
* @version 1.0,2018年2月8日 上午9:37:53
* @remark 数组结构的顺序栈
*/
public class Stack<T> {
private int maxSize; // 栈空间的大小
private T[] stackArray;// 顺序栈,采用泛型确定栈空间的存放的数据类型
private int top; // 栈顶指针
public Stack(int size) {
maxSize = size;
stackArray = (T[]) new Object[maxSize];
top = -1;
}
/**
* 入栈
* @param element
*/
public void push(T element) {
stackArray[++top] = element;
if (top > (maxSize - 1)){
top = maxSize - 1;
}
}
public void pretendedPush() {
++top;
if (top > (maxSize - 1)) {
top = maxSize - 1;
}
}
/**
* 出栈
* @return
*/
public T pop() {
if (top == -1) {
return null;
} else {
return stackArray[top--];
}
}
/**
* 查看栈顶元素,但是不出栈
* @return
*/
public T peek() {
return stackArray[top];
}
/**
* 判断栈是否空
* @return
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 判断栈是否满
* @return
*/
public boolean isFull() {
return top == maxSize - 1;
}
}
package datastructure.part.one;
/**
* @description
* @author Denghs
* @version 1.0,2018年4月5日 下午12:06:32
* @remark
*/
public class TestStack {
public static void main(String[] args) {
Stack<Student> stack = new Stack<Student>(10);
stack.push(new Student("张三"));
stack.push(new Student("李四"));
stack.push(new Student("王五"));
stack.push(new Student("麻六"));
stack.push(new Student("黑七"));
System.out.println(stack.pop().getName());
System.out.println(stack.pop().getName());
System.out.println(stack.pop().getName());
}
}
树形结构属于非线性结构,树中结点之间具有明确的层次关系,并且结点之间有分支。例如权限、行政组织、家谱等。树形结构最大的特点是:**它是一个递归结构。**度、深度、层数、左孩子、右孩子、森林之类的名词,这里不就再解释了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qN2tlyaQ-1591368612826)(http://orxmumro7.bkt.clouddn.com/18-4-19/77582195.jpg)]
二叉树不仅仅只是在树形结构中非常重要,在实际问题解决过程中,往往也是转换为二叉树的形式解决的。**例如像Java中的HashMap,抽象出来的数据结构是红黑树。TreeMap,抽象出来的数据结构是二叉排序树。**关于定义,这里就不多说了。其最大的特点就是每个结点最多只有两棵子树。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eGAao9R-1591368612827)(http://orxmumro7.bkt.clouddn.com/18-4-19/25345859.jpg)]
二叉树的存储方式,分为顺序存储结构跟链式存储结构。顺序存储结构比较浪费空间,基本都是采用链式存储。
顺序存储结构是把二叉树补充成一个完全二叉树,添加一些实际上不存在的虚节点,从根节点开始,一层一层从左往右依次存放到顺序表中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R6eq41t4-1591368612828)(http://orxmumro7.bkt.clouddn.com/18-4-19/24588966.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sj0DSFUG-1591368612829)(http://orxmumro7.bkt.clouddn.com/18-4-19/71655698.jpg)]
二叉树的遍历主要有三种。先序遍历、中序遍历、后序遍历。所谓的先、中、后,都是相对于对应树或子树的根节点而言的。
先序遍历:其规律是根、左、右。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yLs1oBHj-1591368612830)(http://orxmumro7.bkt.clouddn.com/18-4-25/4043005.jpg)]
中序遍历:其规律是左、根、右。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVyeikY1-1591368612831)(http://orxmumro7.bkt.clouddn.com/18-4-25/54019026.jpg)]
后序遍历:其规律是左、右、根。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OoHSINuP-1591368612832)(http://orxmumro7.bkt.clouddn.com/18-4-25/99557798.jpg)]
#include
#include
typedef struct TreeNode{
//数据域
char data;
//左孩子
struct TreeNode * pLeftChild;
//右孩子
struct TreeNode * pRightChild;
}TREENODE, *PTREENODE;
//创建一个树
PTREENODE createTree();
//先序输出
void printXianXu(PTREENODE);
//中序输出
void printZhongXu(PTREENODE);
//后序输出
void printHouXu(PTREENODE);
//计算深度
int binTreeDepth(PTREENODE);
//得到所有的节点数
int sumNodes(PTREENODE rootNode);
//得到所有的叶子节点数
int sumLeafNodes(PTREENODE rootNode);
//copy一个树
PTREENODE copyTree(PTREENODE);
//使用#号创建法,创建一个树
PTREENODE createTree1();
void main(){
int depth, sum, sumLeaf;
PTREENODE tree;
PTREENODE rootNode = createTree();
printXianXu(rootNode);
//printZhongXu(rootNode);
//printHouXu(rootNode);
depth = binTreeDepth(rootNode);
printf("树的深度为:%d\n", depth);
sum = sumNodes(rootNode);
printf("树的节点总数为:%d\n", sum);
sumLeaf = sumLeafNodes(rootNode);
printf("树的叶子节点总数为:%d\n", sumLeaf);
tree = copyTree(rootNode);
printXianXu(tree);
tree = createTree1();
printXianXu(tree);
}
//创建树
PTREENODE createTree(){
PTREENODE pNodeA = (PTREENODE)malloc(sizeof(TREENODE));
PTREENODE pNodeB = (PTREENODE)malloc(sizeof(TREENODE));
PTREENODE pNodeC = (PTREENODE)malloc(sizeof(TREENODE));
PTREENODE pNodeD = (PTREENODE)malloc(sizeof(TREENODE));
PTREENODE pNodeE = (PTREENODE)malloc(sizeof(TREENODE));
pNodeA->data = 'A';
pNodeB->data = 'B';
pNodeC->data = 'C';
pNodeD->data = 'D';
pNodeE->data = 'E';
pNodeA->pLeftChild = pNodeB;
pNodeA->pRightChild = pNodeC;
pNodeB->pLeftChild = pNodeB->pRightChild = NULL;
pNodeC->pLeftChild = pNodeD;
pNodeC->pRightChild = NULL;
pNodeD->pLeftChild = NULL;
pNodeD->pRightChild = pNodeE;
pNodeE->pLeftChild = pNodeE->pRightChild = NULL;
return pNodeA;
}
//先序输出
void printXianXu(PTREENODE rootNode){
if(NULL != rootNode){
//先序访问根节点
printf("%c\n", rootNode->data);
if(NULL != rootNode->pLeftChild){
//再先序访问左子树
printXianXu(rootNode->pLeftChild);
}
if(NULL != rootNode->pRightChild){
//再先序访问右子树
printXianXu(rootNode->pRightChild);
}
}
}
//中序输出
void printZhongXu(PTREENODE rootNode){
if(NULL != rootNode){
if(NULL != rootNode->pLeftChild){
//中序访问左子树
printZhongXu(rootNode->pLeftChild);
}
//再中序访问根节点
printf("%c\n", rootNode->data);
if(NULL != rootNode->pRightChild){
//再中序访问右子树
printZhongXu(rootNode->pRightChild);
}
}
}
//后序输出
void printHouXu(PTREENODE rootNode){
if(NULL != rootNode){
if(NULL != rootNode->pLeftChild){
//后序访问左子树
printHouXu(rootNode->pLeftChild);
}
if(NULL != rootNode->pRightChild){
//再后序访问右子树
printHouXu(rootNode->pRightChild);
}
//再后序访问根节点
printf("%c\n", rootNode->data);
}
}
//深度
int binTreeDepth(PTREENODE rootNode){
int depL, depR;
if(rootNode ==NULL){
return 0;
}else{
//计算左子树的深度
depL = binTreeDepth(rootNode->pLeftChild);
//计算右子树的深度
depR = binTreeDepth(rootNode->pRightChild);
if(depL > depR){
return depL + 1;
}else{
return depR + 1;
}
}
}
//所有的节点
int sumNodes(PTREENODE rootNode){
int sumNodesL, sumNodesR;
if(rootNode ==NULL){
return 0;
}else{
sumNodesL = sumNodes(rootNode->pLeftChild);
sumNodesR = sumNodes(rootNode->pRightChild);
return sumNodesL + sumNodesR + 1;
}
}
//得到所有的叶子节点数
int sumLeafNodes(PTREENODE rootNode){
int sumNodesL, sumNodesR;
if(rootNode ==NULL){
return 0;
}
if(rootNode->pLeftChild == NULL && rootNode->pRightChild == NULL){
return 1;
}else{
sumNodesL = sumLeafNodes(rootNode->pLeftChild);
sumNodesR = sumLeafNodes(rootNode->pRightChild);
return sumNodesL + sumNodesR;
}
}
//copy一个树
PTREENODE copyTree(PTREENODE rootNode){
PTREENODE newNode;
PTREENODE leftNode;
PTREENODE rightNode;
if(rootNode == NULL){
return NULL;
}
//copy左子树
if(rootNode->pLeftChild != NULL){
leftNode = copyTree(rootNode->pLeftChild);
}else{
leftNode = NULL;
}
//copy右子树
if(rootNode->pRightChild != NULL){
rightNode = copyTree(rootNode->pRightChild);
}else{
rightNode = NULL;
}
//创建一个新节点
newNode = (PTREENODE)malloc(sizeof(TREENODE));
newNode->pLeftChild = leftNode;
newNode->pRightChild = rightNode;
newNode->data = rootNode->data;
return newNode;
}
/**
//二叉树的非递归遍历算法
void inorder(PTREENODE rootNode){
//辅助指针变量
PTREENODE node;
//初始化一个栈
STACK stack;
init(&stack);
//把根节点压栈
push(&stack, rootNode);
//栈不为空,就一直循环。知道栈为空
while(!isEmpty(&stack)){
//栈顶的元素不给空,则一直循环
while(getTop(&stack){
//把左子树一直压入栈中
push(&stack, getTop(&stack)->pLeftChild);
}
//执行到这里,说明栈顶是一个NULL元素。左子树为空的节点
//把栈顶的空元素,出栈
node = pop(&stack);
//栈不为空时,说明还没有结束
if(!isEmpty(&stack)){
//左子树没有,访问根节点。栈顶的那个元素则是根节点
printf("%c\n", getTop(&stack)->data);
//访问完根节点,根节点则出栈
node = pop(&stack);
//把右子树压入栈中。执行完这一句,则进行下一个循环了。
push(&stack, node->pRightChild);
}
}
}*/
//使用#号创建法,创建一个树
PTREENODE createTree1(){
PTREENODE node = NULL;
PTREENODE leftNode = NULL;
PTREENODE rightNode = NULL;
char ch;
scanf("%c", &ch);
//#号,则不创建树节点
if(ch == '#'){
return NULL;
}else{
//创建一个节点
node = (PTREENODE)malloc(sizeof(TREENODE));
node->data = ch;
node->pLeftChild = createTree1();
node->pRightChild = createTree1();
return node;
}
}
平衡多路查找树,不是很懂,只知道常用于文件系统。可以参阅《MySQL索引背后的数据结构及算法原理》一文。
把树或森林转换成成对应的二叉树。其转换成二叉树的核心思想是二叉树的结点,左边挂孩子,右边挂兄弟。把二叉树还原成对应的树或森林,也是用同样的原理去还原即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m03ChDvk-1591368612834)(http://orxmumro7.bkt.clouddn.com/18-4-19/55143497.jpg)]
图的算法比较复杂,了解了一些概念以及图常用算法的原理。并没有写出代码。
有向图、无向图、强连通图、存储结构的邻接矩阵和邻接表、遍历算法的深度优先和广度优先、最小生成树的普利姆算法及克鲁斯卡尔算法、最短路径的迪杰斯特拉算法、拓扑排序等。了解一下这些概念,并能在纸上能够大致画出过程图。还真总结不出什么。