回顾数据结构的基础知识

转载出处

本文作者: HuaShan
本文链接: http://denghs.com/2018/02/26/data_struct/

数据结构

之前在写Java中的容器时,有写到一些关于数据结构的部分,可以参考《Java的List、Set、Map容器》一文。这里再详细回顾一下对数据结构的学习。

C语言及指针回顾

字节:存储数据的单元,是硬件所能访问的最小单位。
1字节=8位,1k=1024字节,1M=1024k,1G=1024M。对于4G内存的电脑来说,所能存放数据最多是(4*1024*1024*1024*8)位的二进制数据。
物理内存地址:内存单元的编号,从零开始的非负整数
指针:指针就是地址,地址就是指针。
指针变量:存放内存单元编号的变量,就是存放地址的变量。
结构体:把一些基本类型数据组合在一起,形成一个新的复合数据类型。
一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占4个字节。用第一个字节的地址表示整个变量的地址。一个变量的地址是用该变量首字节的地址来表示。CPU通过地址总线、控制总线、数据总线,俗称三根总线,来操作物理内存。

看懂一个程序的三个步骤

  1. 程序的流程
  2. 每个语句的功能
  3. 试数

问题规模的增加,算法对运算处理的性能消耗

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;
	}
}

B树、B+树

平衡多路查找树,不是很懂,只知道常用于文件系统。可以参阅《MySQL索引背后的数据结构及算法原理》一文。

树、森林与二叉树的转换

把树或森林转换成成对应的二叉树。其转换成二叉树的核心思想是二叉树的结点,左边挂孩子,右边挂兄弟。把二叉树还原成对应的树或森林,也是用同样的原理去还原即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m03ChDvk-1591368612834)(http://orxmumro7.bkt.clouddn.com/18-4-19/55143497.jpg)]

非线性结构-图

图的算法比较复杂,了解了一些概念以及图常用算法的原理。并没有写出代码。
有向图、无向图、强连通图、存储结构的邻接矩阵和邻接表、遍历算法的深度优先和广度优先、最小生成树的普利姆算法及克鲁斯卡尔算法、最短路径的迪杰斯特拉算法、拓扑排序等。了解一下这些概念,并能在纸上能够大致画出过程图。还真总结不出什么。

你可能感兴趣的:(java)