数据结构基础

本文详细介绍了数据结构的基础知识,包括逻辑结构(集合、线性、树形、图形结构)、存储结构(顺序、链式、索引、散列存储)以及数据运算。还探讨了线性表(顺序表和链表)、栈与队列、树和图的基本操作,以及查找和排序算法,如计数排序、选择排序、冒泡排序和插入排序。内容适合初学者入门,有助于理解数据结构的重要概念。
摘要由CSDN通过智能技术生成

数据结构

绪论

0、数据

数据就是能够输入计算机中,并被计算机识别、存储、处理、显示的信息

数据是由数据元素组成

数据元素:是数据的基本单位,数据元素有由若干数据项构成,数据项就是数据元素的最小单位,可以用基本数据类型表示信息的一个属性

1、数据结构的定义

数据结构:研究数据与数据之间的逻辑结构,并研究如何在计算机中存储这种有关系和有关系的数据(存储结构),以及对这些有关系的数据进行操作

2、数据之间的关系分类

分类一

线性关系 (排队)

层次关系 (上下级)

网状关系(地铁线路)

3、数据结构研究内容

研究:逻辑结构、存储结构、数据运算

关系-------逻辑结构

逻辑结构是数据之间的抽象关系

四种逻辑结构:集合结构、线性结构、树形结构、图形结构

集合结构

所有数据元素之间没有任何关系,只不过这些数据元素有共同的属性、特点以及同属于一个整体

线性结构

数据元素之间存在先后顺序。最多1个前驱,一个后继

树形结构

数据元素之间是一种层次关系。一个前驱,n个后继

图形结构

数据元素之间存在多个关系。n个前驱、n个后继

存储--------存储结构

数据逻辑结构在计算机中的映射。就是在计算机中存储数据,并表示出这些数据之间关系

顺序存储---------连续内存空间

将数据元素存储在一段连续的空间中,也就是说这些数据在内存中的存储地址是连续的,只需要知道第一个数据元素的地址,就可以知道其他数据元素的地址。例如数组

将数据元素按照逻辑顺序存储在内存中一段连续的空间,利用内存空间的地址来表示先后顺序

链式存储------------数据的存储空间是随机的

数据元素在内存中的存储地址是随机的,不一定是连续的,(所以不需要一次性全部申请空间,根据需要来申请空间)需要通过在存储数据元素的同时,将下一个数据元素的地址一并存上。这样任然能够表示数据元素之间的关系。

链式存储 : 存储数据元素 + 关系

索引存储

在存储数据元素的同时,还要存储数据的索引在一个索引表中,可以通过索引表得到数据元素之间的关系。例如:电话簿

散列存储

通过数元素中的部分关键字,进行某种运算得到该数据元素的地址。

运算--------数据操作

增、删、该、查

算法

算法定义

算法就是解决某个问题的方法步骤

在计算机中算法是一个有限的语句集合

算法与程序的区别

算法就是解决某个问题的方法步骤,程序是在计算机中以计算机语言具体实现

区别:算法不一定要依赖计算机语言,是一种描述方式。程序是必须要依赖计算机语。算法是有限的,程序可以是无穷的。

算法与数据结构

算法设计:依赖于数据结构的逻辑结构

算法的实现:依赖于数据结构的存储结构

程序=数据结构+算法

算法的好坏判断
  1. 正确性
  2. 效率:消耗时间多少、消耗空间多少
  3. 编程:算法结构好、易于、理解、编写、调试
算法的效率度量
时间复杂度

时间计算:

  1. 事后统计法

    把算法实现,进行运行,得到算法运行消耗的时间。

    **缺陷:**依赖于特定的计算机软硬件,进行大量的统计

  2. 事先估计法

    假设所有计算机执行一条指令的时间都是一致的,只需要统计出执行的语句的次数

    语句频度:算法中一条语句的重复次数

    时间复杂度T(n):所有语句频度之和就是算法的语句执行次数

    在循环中一条语句,执行次数不确定,就用问题规模n来表示

    通常利用事先估计法来表示算法时间。

计算时间复杂度
  1. T(n)表示法:每条语句的语句频度 * 语句条数

  2. 大O表示法O(n):n代表无穷大,并且取T(n)表达式的最高次幂。如果语句执行次数为常数次,那么O(n)=1;

空间复杂度S(N)

占用空间的大小

一、线性表

线性表概念

多个数据元素的逻辑结构为线性关系,这些数据元素之间存在先后顺序,就叫做线性结构,即是线性表。数组就是一个

线性关系:中有且仅有“第一个数据元素”和一个“最后一个数据元素”,中间的数据元素只有一个前驱和一个后继

在存储线性表时,既需要存储具有线性表的数据元素,也需要存储元素数据之间的关系。

总而言之,线性表就是一种逻辑结构为线性结构关系的逻辑结构

线性表的存储结构

线性表的顺序存储:存储数据元素时,把数据元素按照逻辑关系(先后顺序),依次存储在一段连续的内存空间,借助元素在这段连续空间的地址,来表示数据间的先后顺序,这就是顺序结构

顺序表

顺序表 = 数据元素逻辑结构为线性表 + 存储结构为顺序存储

数组就是一个顺序表

顺序表的运算
  1. 添加

  2. 删除

  3. 修改

  4. 查找

  5. 判断满

  6. 判断空

  7. 求表长

  8. 创建表

  9. 清空表

顺序表的实现
  1. 申请一段连续的内存空间来存储数据,通过地址来表示先后关系
  2. 记录表中元素的个数
顺序存储的缺陷
  1. 要申请一段连续的内存空间,申请的时候大小就已经固定了,空间不能动态的添加、删除,这样比较占用内存资源
  2. 插入、删除操作比较麻烦,需要移动整片空间
#include <stdio.h>
#include <stdlib.h>
struct array
{
    int num;
    //struct book Book[50];     //表示为申请了50个连续的空间,每个元素为book
    int no[20];                 //定义一个,int类型的数组来存储每一本书的编号    
    
};

//在堆中动态创建结构体,并返回结构体在堆中的首地址
struct array * creat()
{                                  
    struct array * p=malloc(sizeof(struct array));
    if(p==NULL)
    {
        printf("内存空间申请失败!\n");
        return NULL;
    }
    p->num=0;    
    return p;
}

//判断结构体空间是否为空
int empty(struct array *p)
{
    if(p->num == 0)
    {
        printf("空间为空\n");
        return 1;      //空间为空返回1
    }
    return 0;
}

//判断是否空间是否满了
int full(struct array *p)
{
    if(p->num == 20)
    {
        printf("空间已满\n");         //空间如果满了返回1
        return 1;
    }
    return 0;                       //空间如果没满返回0
}

//添加新的元素
void add(struct array * p,int pos,int shu) //传入结构体的首地址和需要添加元素的位置、添加的元素的值
{
    //判断空间是否为满了
    if(full(p))
    {
        return ;            //如果空间满了,那就自接退出,不操作
    }
    
    //判断空间是否为空
    if(empty(p))          //空间为空,那么就将插入的数据添加到no数组第0个元素位置
    {
        p->no[0]=shu;
        p->num ++;
        return  ;
    }
    
    //判断插入下标是否越界
    if(pos > p->num -1 )
    {
        printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
        printf("插入的数据下标为%d超出下标范围,将该数据加在最后一个数据元素后面\n",pos);
        putchar('\n');
        p->no[p->num]=shu;      //越界了要将数据添加在最后一个元素后面,而不是覆盖最后一个元素p->no[p->num-1]
        p->num ++;
        return ;         //插入元素下标越界,结束添加功能
    }
         for(int i = p->num - 1;i >= pos ;i--)     //将pos处以及之后的元素后一位,空出来给添加的元素
    	{
        	p->no[i+1]=p->no[i];
    	}
        p->no[pos]=shu;
    	p->num ++;
}

//删除执行位置的数据元素
 void del(struct array *p,int pos,int * k)
{
     //判断是否为空
    if(empty(p))
    {
        return ;
    }
    
     //判断下标是否越界
    if(pos >= p->num -1)
    {
        printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
        printf("删除的数据下标为%d超出下标范围,将k的值赋值为0\n",pos);
        putchar('\n');
        *k=0;
        return ;         //插入元素下标越界,结束添加功能
    }
    
     *k=p->no[pos];
      printf("需要删除下标为%d,数据为%d\n",pos,*k);       
    for(int i=pos;i< p->num-1;i++)        //将pos之后的第一个元素赋值给pos的位置,其他元素一次前移一个位置
    {                        //检验判断的条件是,将条件带入到式子中,最后一次前移是,p->nos[num-2]=p->nos[num-1];
        p->no[i]=p->no[i+1];
    }
     printf("删除成功\n");
     p->num --;
}

//查找数据元素
void reserch(struct array *p,int pos)
{
    //判断查看的下标是否越界
    if(pos > p->num -1)
    {
        printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
        printf("查看的数据下标为%d超出下标范围,自接退出查找\n",pos);
        putchar('\n');
        return ;         //插入元素下标越界,结束添加功能
    }
    printf("查看的数据元素是下标为%d的元素数据%d\n",pos,p->no[pos]);
}

//修改数据元素
void change(struct array *p,int pos,int shu)
{
    //判断查看的下标是否越界
    if(pos > p->num -1)
    {
        printf("现在有%d个数据,下标范围是0~%d\n",p->num,(p->num) -1);
        printf("查看的数据下标为%d超出下标范围,自接退出修改\n",pos);
        putchar('\n');
        return ;         //插入元素下标越界,结束添加功能
    }
    printf("修改前:%d\n",p->no[pos]);
    p->no[pos]=shu;
    printf("修改完成\n");
    printf("修改后:%d\n",p->no[pos]);
}

//数据显示
void show(struct array *p)
{
    for(int i=0;i<p->num;i++)
    {
        printf("%d  ",p->no[i]);
    }
    putchar('\n');
}

int main()
{
    int k=0;
    struct array *p=creat();
    	show(p);
        add(p,10,1);
        add(p,10,2);
    	add(p,10,3);
    	add(p,10,4);
    	add(p,10,5);
    	add(p,10,6);
        show(p);
    	del(p,0,&k);
    	show(p);
    	add(p,4,7);
    	show(p);
    	reserch(p,5);
    	change(p,0,8);
    	show(p);
}

链表

链式存储:是一种不需要连续空间存储数据的数据结构,需要动态存储数据内容和数据之间的先后关系。

总而言之:链表就是一种

链表的概念

链表是一种数据结构,它的逻辑结构为线性表,存储结构为链式存储。数据元素之间的位置是任意的(需要有这个元素才动态申请存储空间),一个数据元素由数据内容+关系组成。其中关系是通过指针来表示,这个指针存储下一个数据元素的地址。

链表 = 线性表 + 链式存储

链表组成
  1. 头指针:是一个指针,存储链表中第一个元素的地址,方便指名链表的位置,之后可以通过这个指针来访问其他数据元素。

  2. 首元素节点

    真正存储数据的节点

  3. 头节点

    在链表,通常会添加一个不存储数据的节点,就是空节点,作为链表的开始节点,真正存储数据的节点是第二个节点。这样的话就可以解决删除和添加数据元素到首节点位置时,要改变头指针指向。加了头节点的话删除首节点就和删除其他节点一样,只需要改变数据元素中的指针的指向。

链表的创建
链表元素的插入和删除位置固定

链表元素的插入和删除都是第一个元素位置。这时就不需要

利用for循环去找插入位置前一个元素的地址。因为for循环的时间赋复杂度为n,字节添加和删除首节点位置数据元素的操作时间复杂度为1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//声明结构体
struct node
{
	char name[20];
	struct node *prior ,* next;
};

//常见链表的头节点
struct node *  create()
{

	struct node * head = malloc(sizeof(struct node));

	if(head == NULL)
	{
		printf("malloc linklist error\n");
		return NULL;
	}

	head->prior = NULL;
	head->next = NULL;

	return head;
}


//添加数据元素
void insert_list(struct node * head,int pos,char * data)
{

	struct node * p = head;
    
    //寻找pos-1的位置地址------------如果选择添加在首节点位置,那么可以不需要写这些
	/*int i = 0;
	while(i <pos -1 && p->next != NULL)
	{
		p = p->next;
		i++;
	}
	if(p->next != NULL)             //判断P指向的是否为最后一个元素
	{
		p->next->prior = new;
	}*/
    
    struct node * new = malloc(sizeof(struct node ));
	new->next = p->next;
	p->next = new;
	new->prior = p;

	strcpy(new->name,data);

}

//判断是否空间没有存储数据元素
int empty(struct node * head)
{

	if(head->next == NULL)
	{
		printf("is empty\n");
		return 1;
	}
	
	return 0;
}


//删除数据元素
void delete_list(struct node * head,int pos)
{	

	if(empty(head))
	{
		return ;
	}

	struct node * p = head;
/*
	/*int i = 0;
	while(i < pos - 1 && p->next != NULL)                  //让指针p指向pos-1这个地址
	{
		p = p->next;
		i++;
	}

	if(p->next == NULL)      /*功能:判断我要删除的pod这个位置的元素是否为空,为空就退出.
						前面已经有这个判断条件了,而且此时的指针p已经指向最后一个元素的地址了,为什么这里还要判断一次?
					因为;在while中,当p指向pos-1的地址时,i也变为了pos-1,先判断i<pos-1,不满足为假,自接结束循环,不					    会在判断p->next!=NULL这个条件
	{
		printf("pos is not found\n");
		return ;
	}
*/
	struct node * q = p->next;
	p->next = q->next;
	if(q->next != NULL)
	{
		q->next->prior = p;
	}

	printf("delete data is : %s\n",q->name);
	free(q);

}

//修改数据元素------通过查询位置编号
void update_pos_list(struct node * head,int pos ,char * newdata)
{

	struct node * p = head;
	int i = 0;
	while(i < pos)
	{
		p = p->next;
		if(p == NULL)
		{
			printf("error pos\n");
			return ;
		}
		i++;
	}
	
	strcpy(p->name,newdata);
}

//修改数据元素----------通过查询数据元素内容
void update_data_list(struct node * head,char * data,char * newdata)
{
	/*
	struct node * p = head->next;

	while(p != NULL)
	{
		if( strcmp(p->name,data) == 0 )
		{
			strcpy(p->name,newdata);
		}
		p = p->next;
	}
	*/

	struct node * p = head;

	while(p->next != NULL)
	{
	
		p = p->next;
		if( strcmp(p->name,data) == 0 )
		{
			strcpy(p->name,newdata);
		}
	}
}

//
void show_list(struct node * head)
{
	struct node * p = head;

	while(p->next != NULL)
	{
		p = p->next;
		printf("%s\t",p->name);
	}
	printf("\n");
}


int main()
{

	struct node * head = create();

	delete_list(head,5);

	insert_list(head,10,"zhangsan");
	insert_list(head,10,"laoliu");
    insert_list(head,1,"wangwu");
    insert_list(head,1,"laozhang"); 
	show_list(head);
	
    delete_list(head,5);
    show_list(head);
    delete_list(head,5);
    show_list(head);
}

#include <stdio.h>
#include <stdlib.h>

int m=0;
struct node 
{
    int date;
    struct node * next;
};

struct node * creat()
{
    struct node * p = malloc(sizeof(struct node));
    if(p == NULL)
    {
        printf("申请空间失败\n");
        return NULL;      //可以写return head;      吗?
    }
    p->next = NULL;
    return p;
}

void add_list(struct node * head,int pos,int new_date)
{
    struct node * p = head;
    int i=0;
    while(i<pos-1 && p->next != NULL)
    {
        p = p->next;
        i++;
    }
    
    struct node * new = malloc(sizeof(struct node ));
    new->next = p->next;
    p->next = new;
    new->date = new_date;
    m++;
    printf("m=%d\n",m);
    return ;
}

void show_list(struct node * head)
{
    if(head->next == NULL)
    {
        printf("没有元素,无法显示\n");
        return ;
    }
    
    struct node * p = head->next;
    while(p != NULL)
    {
        printf("%d  ",p->date);
        p = p->next;
    }
    putchar('\n');
    return ;
}

//判断空间为空
int empty(struct node * head)
{
    if(head->next == NULL)
    {
        printf("空间为空\n");
        return 1;
    }
    return 0;
}
//从小到大排序
void paixu(struct node * head)
{
    if(empty(head))
    {
        return ;
    }
    struct node * p = head->next;
    
    if(p->next == NULL)
    {
        printf("只有一个元素\n");
        printf("%d\n",p->date);
        return ;
    }
    for(int i=0;i<m-1;i++)
    {
        for(int j=0;j<m-i-1;j++)
        {
            if(p->date > p->next->date)
            {
                int a=0;
                a = p->date;
                p->date = p->next->date;
                p->next->date = a;
            }
            p = p->next;
            //printf("此时p指向的地址里面的值为%d\n",p->date);
        }
        p=head->next;
    }
}

int main()
{
    struct node * head = creat();
    show_list(head);
    for(int i=0;i<10;i++)
    {
        int pos = 0,date = 0;
        printf("请输入需要添加的位置和数据\n");
        scanf("%d%d",&pos,&date);
        add_list(head,pos,date);
    }
    show_list(head);
    paixu(head);
    show_list(head);
}
循环链表
循环链表的概念

最后一个数据元素指向的地址为头节点的地址

if(p->next == NULL)
{ 
    p->next = head;
}

//判断空
if(head->next == head)
{
    
}

//修改头节点的next和prior指向就行
struct node * creat()
{
    struct node * head = malloc(sizeof(struct node));
	//就不存在指针指向空的情况
	head -> next = head;
	head -> prior = head;
}

//以后在判断最后一个数据元素的时候只需要判断p->next是否为head,不在是判断为NULL


链表和顺序表的优缺点

线性表优点
  1. 连续空间存储数据元素,可以通过地址来表示逻辑关系

  2. 查看和修改数据元素的时候方便,只需要查看下标就行。

    //例如查看下标为pos这个元素
    
    p->nos[pos](前提是pos< p->num-1);
    
    //修改下标为pos这个元素
    
    p->nos[pos] = date;
    
线性表的缺点
  1. 增加和删除元素,时间复杂度很高,O(n)。原因:
    1. 1 添加一个元素到pos这个位置,需要利用循环,从p->nos[p->num-1] 这个元素开始,直到p->nos[p->pos]为止,将每一个元素向后移动一位才行。
    2. 1同理删除pos这个位置的元素,需要利用循环,从p->nos[p->pos+1]这个元素开始,到p->nos[p->num-1]这个元素为止,将每一个元素向前移动一位
链表的优点
  1. 添加和删除元素方便,时间复杂度为O(1)

    //添加一个元素------------默认是头插法,就是每次都是从头结点之后插入元素
    struct node * p = head;
    new->next = p->next;
    p->next = new;
    
    //删除一个元素----------头删法
    struct node * p = head;
    struct node *q = p->next;
    p->next = q->next;
    
    
  2. 动态申请空间,不需要空间连续,元素多少可以随时变化,要多少空间就开辟多少空间

链表的缺点
  1. 修改和查询元素时间复杂度O(n)。需要利用循环从头开始取寻找需要修改或者查询的位置。

    //修改元素
    struct node * p = head;
    while(p->next !=  NULL)
    {
        p=p->next;
        p->date = new_date;
    }
    

二、栈与队列

栈的概念

栈就是一个只允许固定位置操作的线性表,允许操作的一端叫做栈顶,不允许操作的一端叫做栈底。遵循先进后出的原则。

每当操作(删除或者插入)一个栈顶元素,就会有一个新的栈顶元素。

顺序栈

逻辑结构: 栈

存储结构:顺序存储

顺序栈的概念

开辟一个连续的内存空间,规定一端为不可操作的栈底,将栈顶指向栈底的位置,这样添加数据元素的时候任然满足在栈顶操作,之后每添加一个元素,栈顶就加一,始终保持栈顶指向下一个将要添加的位置。如果想要删除操作,那么只需要将栈顶减1,因为此时的栈顶是指向下一个将要添加元素的位置,而且在栈顶减1后,栈顶指向被删除的元素的地址,下一次如果是添加元素,那么就会添加在当前栈顶的位置,也就是要被删除的元素的地址,这样添加的元素就会覆盖掉之前的要被删除的元素;如果是删除元素,任然需要先将栈顶地址减1,此时栈顶位置已经变了,不会在指向上一次要删除的元素的地址。

#include <stdio.h>
#include <stdlib.h>

struct node 
{
  	int date[10];
    int top;          //这里是声明一个栈顶指针top,由于数组名加上下标(date+top)就是元素地址,所以没有用int * top的形式
};

//创建一个空栈,里面可以存10个int类型的数
struct node * creat()
{
    struct node * p = (struct node *)malloc(sizeof(struct node));
    p->top = 0;
    return p;
}

int full(struct node *p)
{
    if(p->top ==(sizeof(p->date) / sizeof(int)))
    {
        printf("栈空间已满\n");
        return 1;
    }
    return 0;
}

//判断栈是否为空
int empty(struct node *p)
{
    if(p->top == 0)
    {
        printf("栈空间为空\n");
        return 1;
    }
    return 0;
}
//从栈顶添加元素
void add_stack(struct node * p,int date)
{
    if(full(p))
    {
        return ;
    }
    p->date[p->top] = date;
    p->top ++;
}

//从栈顶出栈
void pop_stack(struct node * p)
{
    if(empty(p))
    {
        return ;
    }
    
    p->top --;
    printf("需要删除的元素为%d\n",p->date[p->top]);
}

//显示栈顶
void show_top(struct node *p)
{
    printf("栈顶存放的元素是:%d\n",p->date[p->top -1]);
    return ;
}

//遍历栈中所有元素
void show_stack(struct node * p)
{
    int i = p->top -1;
    while(i >= 0)
    {
        printf("%d  ",p->date[i]);
        i--;
    }
    putchar('\n');
}

int main()
{
    struct node * p = creat();
    add_stack(p,10);
    add_stack(p,20);
    add_stack(p,30);
    add_stack(p,40);
    add_stack(p,50);
    show_top(p);
    show_stack(p);
    pop_stack(p);
    show_stack(p);
    
}

链式栈

链式栈的逻辑结构为:栈

存储结构为:链式存储

链式栈的栈顶在头节点之后的第一个元素位置,也就是head ->next

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//声明结构体
struct node
{
    int date;
    struct node * next;
};

//创建空栈------头节点
struct node * creat()
{
    struct node * head = (struct node * )malloc(sizeof(struct node));
    if(head == NULL)
    {
        printf("空间申请失败\n");
        return NULL;
    }
    head->next = NULL;
    return head;
}

//在栈顶添加元素-------------就是在head->next 指向的地址处进行操作
void add_stack(struct node * head,int date)
{
    struct node * new = (struct node *)malloc(sizeof(struct node));
    new->next = head->next;
    head->next = new;
    new->date = date;
}

//判断栈空间是否为空
int empty(struct node *head)
{
    if(head->next == NULL)
    {
        printf("栈空间为空,无法删除\n");
        return 1;
    }
    return 0;
}
//删除元素
void pop_stack(struct node * head)
{
    if(empty(head))
    {
        return ;
    }
    
    struct node * q = head->next;
    head->next = q->next;
    free(q);
}

//遍历栈空间元素
void show_stack(struct node * head)
{
    struct node * p = head->next;
    while(p->next != NULL)
    {
        printf("%d  ",p->date);
        p = p->next;
    }
    putchar('\n');
}


int main()
{
    struct node *head = creat();
    pop_stack(head);
    add_stack(head,10);
    add_stack(head,20);
    add_stack(head,30);
    add_stack(head,40);
    add_stack(head,50);
    
    show_stack(head);
    pop_stack(head);
    show_stack(head);
}

判断括号匹配问题 -------方式一:利用链式栈

#include <stdio.h>
#include <stdlib.h>

struct node 
{
    char date;
    struct node *next;
};

int main()
{
    char str[10];
    int i=0;
    int num=0;
    printf("请输入:\n");
    scanf("%s",str);
    printf("输入的结果%s\n",str);
     while(str[i] != '\0')
    {
            if(str[i]!='[' || str[i]!='(' || str[i]!='<' || str[i]!='{'  )
        {
            num ++;
        }
        if(str[i]!=']' || str[i]!='}' || str[i]!='>' || str[i]!=')')
        {
            num ++;
        }
         i++;
    }
    if(num!= 0)
    {
         printf("不匹配\n");
        return 0;
    }
    struct node * p = malloc(sizeof(struct node ));
    p->next = NULL;
   
    for(int i = 0;i < str[i] != '\0';i++ )
    {
    if(str[i] == '[' || str[i] == '{' || str[i] == '<' || str[i] =='(')
    {
        struct node *new = malloc(sizeof(struct node));
        new->next = p->next ;
        p->next = new;
        new->date = str[i];
        printf("添加%c\n",new->date);
        continue;
    }
    else if(str[i] -1 != p->next->date  && str[i] -2 != p->next->date  )
    {
        printf("不匹配\n");
        //printf("栈顶的元素%c\n",p->next->date);
        break;
    }else if(p->next->date == '{' && str[i] == '}')         //配对{}
    {
        struct node * q = p->next;
        p->next = q->next;
        printf("%c出栈\n",q->date);
        free(q);
        if(p->next == NULL)
        {
            printf("元素全部出栈,匹配成功\n");
            break;
        }
        continue;
    }  //匹对[]
        else if(p->next->date == '[' && str[i] == ']')
    {
        struct node * q = p->next;
        p->next = q->next;
        printf("%c出栈\n",q->date);
        free(q);
        if(p->next == NULL)
        {
            printf("元素全部出栈,匹配成功\n");
            break;
        }
        continue;
    }       //匹对()
        else if(p->next->date == '(' && str[i] == ')')
    {
        struct node * q = p->next;
        p->next = q->next;
        printf("%c出栈\n",q->date);
        free(q);
        if(p->next == NULL)
        {
            printf("元素全部出栈,匹配成功\n");
            break;
        }
        continue;
    }
        //匹对<>
        else if(p->next->date == '<' && str[i] == '>')
    {
        struct node * q = p->next;
        p->next = q->next;
        printf("%c出栈\n",q->date);
        free(q);
        if(p->next == NULL)
        {
            printf("元素全部出栈,匹配成功\n");
            break;
        }
        continue;
    }
 
    }
}

判断括号匹配问题 -------方式二:利用顺序栈

#include <stdio.h>
#include <stdlib.h>
struct stack
{
    int top;
    char str[10];
};

int main()
{
    char ch[10] ;
    struct stack * p = malloc(sizeof(struct stack));
    p->top = 0;
    printf("输入符号\n");
    scanf("%s",p->str);
    
    for(int i=0;ch[i] != '\0';i++)
    {
    if(p->str[p->top]!= ']' && p->str[p->top] != '}' && p->str[p->top] != ')' && p->str[p->top] != '>')
    {
        p->str[p->top] = ch[i];
        p->top ++;
            
    }else
    {
        p->top --;
        if(ch[i]-1 != p->str[p->top] && ch[i]-2 != p->str[p->top])
        {
            printf("不匹配\n");
            break;
        }
    }
    
    }
    
}

入栈出栈顺序

只能对栈顶进行操作。一组数据按照顺序入栈,但是出栈可以随时,就是说还有其他数据没有入栈,在栈中的数据就可以出栈。出栈n个数据之后继续入栈其他元素,中途又可以出栈。这样的话,最终出栈得到的元素序列就不定

队列

一种特殊的线性表,只允许在线性表两端进行操作,插入端称为队尾,删除端叫做队头。满足先进先出原则

链队列

逻辑结构为队列(线性表)

存储结构为链式存储--------------插入端称为队尾,删除端叫做队头。有一个对头指针和一个队尾指针,对头指针指向头节点,队尾指针指向需要添加的元素的前一个元素的地址。

在空链队列是,对头和队尾指针都指向头节点。之后每 添加一个元素都需要将队尾指针指向新添加的元素的地址。

三、树的基本操作

树相关的概念

树是一种描述数据和数据之间的关系、存储、运算的数据结构。是节点数大于等于0的有限节点的集合,树只有一个根节点,其他的节点被分为互不相交的有限个集合,这些集合本身也是一颗树,这些树都是根的子树。

树描述的关系是一种层次关系,或者叫做从属关系

树有层次,最大的层叫做树的层

结点的子结点叫做该节点的度,整个树中最大的节点的度叫做树的度,度为0的节点叫做终端节点

每一个节点最多只有两个子节点的树

树中的每个元素都叫做结点

结点的度:一个结点包含的子树的个数

叶子结点(终端结点):度为0的结点

子结点:结点有度,有子树,子树的根节点就是这个结点子结点父结点

兄弟结点:同属于一个父结点

树的度:在树中,最大结点的度,称为整棵树的度

结点的层次:从整棵树的根结点开始,叫做第一层,根的子结点就是第二层,依次加1

树的深度:树中结点层次最大的叫做树的深度

路径:从一个结点到另一个结点,经过的位置叫做路径

路径的边:

路径长度:经过的边的条数叫做路径长度

有序树:(兄弟结点不能交换顺序)根结点的子树不能交换

顺序树:就是一种一对多的关系,结点只有一个前驱,可以有多个后继

树的存储

链式存储:设计每个结点按照树的度设计指针空间(关系),来存储下一层的子结点的地址

在这里插入图片描述

二叉树的概念

对于树而言,存储过于繁琐,操作过于复杂,所以想设计一种简单的树来进行表示操作存储。只需要把普通的树转换为这种简单的二叉树就可以了。

二叉树树中的每个结点最多只有两个子树(两个子结点),且严格区分左右子树,不能交换

  1. 二叉树的第i层,最多有2^(i-1)个结点

  2. 深度为k的二叉树,最多有2^k - 1 个结点

  3. 对于二叉树,叶子结点数n0,度为二的结点数n2,n2 = n0 - 1

  4. 满二叉树:每层都是2^(i-1)个结点,每层都是最多结点

  5. 完全二叉树:如果有k层,从第一层到k-1层,每一场都是2^(k-1),第k层,一定是从左至右每个位置都有结点(不一定满),也不一定是必须一左一右两个终端节点,也可以是一个左,但是这个节点就没有兄弟节点了,因为已经空了右节点。

二叉树的存储

在二叉树中,从下标为1开始存储,根结点存储下标为1位置,

当前结点的左孩子存储 2 * i(i就是结点的下标),

结点右孩子存储在 2 * i + 1位置

父节点:当前结点的位置 / 2 ,就是父结点的位置

如果i从0开始,那么

当前结点的左孩子存储 2 * (i+1)-1==2i+1(i就是结点的下标),

结点右孩子存储在 2 * (i + 1) +1 -1 == 2i +2位置

父节点:当前结点的位置 / 2 ,就是父结点的位置

二叉树的遍历

  1. 顺序栈遍历:先将根节点遍历根节点(根节点出栈),把它左节点入栈,然后右节点入栈,之后右节点处于栈顶,先对器进行出栈,同理将有节点的左右子节点入栈,然后遍历右子节点(出栈),后续同上操作。直到所有节点全部遍历完(出栈)。
  2. **顺序队列遍历(层次遍历):**先遍历根节点(根节点出栈),
  3. 先序遍历:根------左子树---------右子树
  4. 中序遍历:左子树----根-----右子树
  5. 后序遍历:左子树-------右子树------根

利用先序+中序 或者后序+中序的方式可以还原二叉树结构,得到另外一个遍历顺序的结果

具体方法是:

  1. 利用先序的第一个元素就是根节点,找到根节点。
  2. 然后再中序中判断其余元素是根的左子树还是由子树成员
  3. 继续看先序中的下一个元素,并在中序中找到该元素,然后以它为根,利用左–根–右的顺序判断该元素两边的元素在该元素的左边还是右边
  4. 继续看先序中的下一个元素,,并在中序中找到该元素,然后以它为根,利用左–根–右的顺序判断该元素两边的元素在该元素的左边还是右边
  5. 直到全部元素遍历完

哈夫曼树

在树中,权值(比重)大的数据放在二叉树的最上层,权值小的放在二叉树的最下层。

带权路径长度:节点的权值 * 根节点到该节点的边数

带权路径长度就可以反应某个节点访问的难以程度

所有要访问的数据都是叶子节点(就是度为0的节点),其余节点就是这个树所占的权重。

四、图的概念

图的存储

邻接矩阵
邻接链表

五、查找、排序算法

计数排序

计数排序:利用待排序元素中最大的元素的值m,来创建一个m+1这么大的一个数组b,然后按顺序将待排序的元素的值,当做b数组的下标,找到该下标对应的元素,对其加1。如果在待排序元素中一个数出现n次,那么就需要将b数组中该元素值为下标的数加n。待排序元素中没有的数,在数组b中就是0;所有带排序元素都遍历完后,将数组b遍历。利用for循环判断条件是b[ i ] != 0,就打印下标 i, 之后b[ i ] - - ,直到不满足条件

#include <stdio.h>

int main()
{
    int a[5] = {4,2,6,4,90};
    int max=0;
    for(int i = 0; i< sizeof(a) / sizeof(int) ;i++)
    {
        if(a[i] > max)
        {
            max = a[i];
        }
    }
    printf("max=%d\n",max);
    int n=max+1;
    int b[n];
    bzero(b,n*4);           //关键之处,不能将int b[n];写成int b[n]就不管了,因为此时数组大小是个未知数,这种定义方式无异于int b[];这种方式,都是不允许的。需要利用bzero()函数加数组中的所有元素都赋值为0
    
    for(int i = 0; i < sizeof(a) / sizeof(int); i++)
    {
        if(a[i] >= 0)
        {
            b[a[i]]++;
        }
    }
    
    for(int i = 0; i<sizeof(b) / sizeof(int) ;i++)
    {
		while(b[i] != 0)
        {
            printf("%d ",i);
            b[i]--;
        }
    }
    putchar('\n');
}

选择排序

一种与冒泡排序很像的排序。他是将第一个元素和该元素之后的每一个元素进行比较,如果被比较的元素大于(小于)比较的元素,那么就交换值的大小,之后被比较元素继续用新的值和之后的元素比较,直到它和所有元素都比完。此时被比较元素一定是所有比较元素中最小的。之后利用该方法,取出第二个元素,然它和它之后的每一一个元素比较,方法一样。

和冒泡拍序不同之处在于,里层循环的条件是从i+1开始,小于带比较元素的个数

#include <stdio.h>

int main()
{
    int a[5] = {1,4,6,3,7};
    for(int i = 0; i < sizeof(a)/sizeof(int ) - 1; i++)
    {
        for(int j = i+1; j<sizeof(a) / sizeof(int); j++)
        {
            if(a[i] > a[j])
            {
                int b = a[i];
                a[i] = a[j];
                a[j] = b;
            }
        }
    }
    
    for(int i = 0;i < sizeof(a) / sizeof(int);i++)
    {
        printf("%d ",a[i]);
    }
    putchar('\n');
}

冒泡排序

从第零个元素开始,将前一个元素和它后面一个元素进行比较,如果前一个元素大于后一个元素,那么交换位置(交换值),依次类推,直到倒数第二个元素和倒数第一个元素比较完,此时最后一个元素一定是所有元素中最大的。之后在进行第二轮,从第零个元素开始,前一个元素和它后面一个元素进行比较,如果前一个元素大于后一个元素,那么交换位置(交换值),依次类推,直到倒数第二个元素和倒数第一个元素比较完(为了减小时间复杂度,需要减i

),此时最后一个元素一定是所有元素中最大的

#include <stdio.h>

int main()
{
    int a[5] = {2,6,3,5,1};
    for(int i =0;i<sizeof(a) / sizeof(int) -1 ;i++)             //有N个数需要排序,那么就需要循环N-1次
    {
        for(int j = 0;j < sizeof(a) / sizeof(int) -1 -i; j++)       
        {
            if(a[j] > a[j+1])
            {
                int b = a[j];
                a[j] = a[j+1];
                a[j+1] = b;
            }
        }
    }
    for(int i = 0; i < sizeof(a) / sizeof(int) ; i++)
    {
        printf("%d ",a[i]);
    }
    putchar('\n');
}

插入排序

#include <stdio.h>

int main()
{
    int a[5] = {2,1,4,23,5};
    for(int i = 1;i < sizeof(a) / sizeof(int );i++)
    {
        int temp = a[i];
        int j = i;
        while(j >0)
        {
            if(a[j-1] > temp)
            {
                a[j] = a[j-1];
            }else
            {
                break;
            }
            j--;
        }
        a[j] = temp;
    }
    for(int i=0 ; i<sizeof(a) / sizeof(int);i++)
    {
        printf("%d ",a[i]);
    }
    putchar('\n');
}

折半查找

#include <stdio.h>

int main()
{
    int a[10] = {1,4,6,2,7,3,8,4,5,3};
    int i = 0,j = 9;
    int mid = (i + j) / 2;
    int num =0;
    scanf("%d",&num);
    
    while(i <= j)
    {
        if(num == a[mid])
        {
            printf("找到,位置为%d\n",mid);
            break;
        }
        else if(num > a[mid])
        {
            i = mid +1;
        }else
        {
            j = mid -1;
        }
        mid = (i + j) / 2;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值