数据结构

本文详细介绍了数据结构中的数组、链表、栈、队列、树和图的基本概念、优缺点以及操作。数组在内存中是连续的,查找速度快但插入删除效率低;链表动态分配内存,插入删除高效但查找慢。栈遵循先进后出的原则,而队列则是先进先出。树中包括二叉树的概念,如完全二叉树、满二叉树等,并讲解了图的表示方法和遍历策略,如深度优先搜索和广度优先搜索。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数组

1.数组的名字也是一个指针,该指针指向数组的第一个元素,但是C++中并没有记录数组的大小,因此访问时需要确定没有超出数组的边界,例如,a[3]超出了边界

int a[] = {1,2,3}; //a为指针

2.数组的定义

//普通一维数组
int arr[10];
int a[] = {1,2,3};
//普通二维数组
int arr[2][2] = 
{
	{1,1},
	{1,1}
};
//一维动态数组
#include<iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    //分配动态一维数组 
    int *arr=new int[n];
    
    for(int i=0;i<n;i++)
        cin>>arr[i];
    for(int i=0;i<n;i++)
       cout<<arr[i]<<" ";
    //释放arr数组 
    delete[] arr;
    return 0;
}
//二维动态数组
#include<iostream>
using namespace std;
int main()
{
    int row,col;
    cin>>row>>col;
    //为行指针分配空间 
    int **arr=new int *[row];    
    for(int i=0;i<row;i++)
         arr[i]= new int[col];//为每行分配空间(每行中有col个元素) 
    //输入二维数组的数 
    for(int i=0;i<row;i++)
        for(int j=0;j<col;j++) 
        cin>>arr[i][j];
    cout<<"*******************"<<endl;
     //输出二维数组中的数  
    for(int i=0;i<row;i++)
    {
         for(int j=0;j<col;j++) 
          cout<<arr[i][j]<<" ";
        cout<<endl;
    } 
    //释放二维数组(反过来) 
    for(int i=0;i<row;i++)
        delete[] arr[i]; 
    delete[] arr;
    return 0;
}

3.在C/C++中,当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针,详见《剑指offer》38页

int GetSize(int data[])
{
	return sizeof(data);
}

int main()
{
	int data1[] = {1,2,3};
	int size1 = sizeof(data1);	//size1=12
	
	int* data2 = data1;
	int size2 = sizeof(data2);	//size2=4
	
	int size3 = Getsize(data1);	//size3=4
	
	return 0;
}

4.vector是一种动态数组,其详细使用介绍详见标准模板库STL_Haocan Xu

链表

链表与数组
1. 区别
1.1 数组静态分配内存,链表动态分配内存。
1.2 数组在内存中是连续的,链表是不连续的。
1.3 数组利用下标定位,查找的时间复杂度是O(1),链表通过遍历定位元素,查找的时间复杂度是O(N)。
1.4 数组插入和删除需要移动其他元素,时间复杂度是O(N),链表的插入或删除不需要移动其他元素,时间复杂度是O(1)。
2. 数组的优缺点
2.1 优点1——随机访问性比较强,可以通过下标进行快速定位。
2.2 优点2——查找速度快
2.3 缺点1——插入和删除的效率低,需要移动其他元素。
2.4 缺点2——会造成内存的浪费,因为内存是连续的,所以在申请数组的时候就必须规定七内存的大小,如果不合适,就会造成内存的浪费。
2.5 缺点3——内存空间要求高,创建一个数组,必须要有足够的连续内存空间。
2.6 缺点4——数组的大小是固定的,在创建数组的时候就已经规定好,不能动态拓展。
3. 链表的优缺点
3.1 优点1——插入和删除的效率高,只需要改变指针的指向就可以进行插入和删除。
3.2 优点2——内存利用率高,不会浪费内存,可以使用内存中细小的不连续的空间,只有在需要的时候才去创建空间。大小不固定,拓展很灵活。
3.3 缺点1——查找的效率低,因为链表是从第一个节点向后遍历查找。

  1. 链表的定义
//链表的定义
struct node{
    int data;         //数据域 
    node *next;       //指针域,只想下一个元素 
}; 
  1. 创建新链表
//链表的创建
node* creatlist(int arr[],int len){    //建立链表 
    node*head,*pre,*p;
    head=new node;           //创造头结点 
    head->next=NULL;
    pre=head;                //pre赋值 
    for(int i=0;i<len;i++){
        p=new node;
        p->data=arr[i];      //赋值给数据域 
        p->next=NULL;         //最后一个节点的指针域置空 
        pre->next=p;          //新结点连到链表的结尾 
        pre=p;                //pre指向链表的最后一个结点 
    } 
    return head;
}
  1. 插入
//链表的插入
void insert(node*head,int pos,int x){       //实现将一个数据插入到链表的指定位置pos处 
    node*p,*pre;
    pre=head;
    p=new node;      //分配新结点 
    p->data=x;
    while(--pos){      //将pre指针定位到pos位置的前面 
        pre=pre->next;
    }
    p->next=pre->next;    //先将pre的next赋值给p ,将pre所指结点后面所有的结点连接到p所指的结点 
    pre->next=p;          //将p所指的结点链接到pre所指结点后面。 
}
  1. 查找
//链表的查找
int search(node*head,int x){       //实现在链表中查找数据域等于x的结点的个数 
    int count=0;                   //计数器,初始化为零 
    node*p=head->next;
    while(p!=NULL){
        if(p->data==x){        //找到一个结点,计数器加一 
            count++;
        }
        p=p->next;           //p往后挪一个位置 
    }
    return count;            //返回查找结果 
 
}
  1. 删除
//链表的删除
void del(node*head,int x){   //实现将链表所有数据域等于x的结点删除 
    node *pre,*p;
    pre=head;            //pre指针始终指向被删除结点的前置结点 
    p=pre->next;         //p指针为工作指针,用于遍历链表 
    while(p!=NULL){
        if(p->data ==x){          //如果是要被删除的结点 
            pre->next =p->next;    
            delete(p);             //要记得用过的内存还给操作系统 
            p=pre->next ;         //p指针更新到pre指针的后面 
        } 
        else {  
            pre=p;         //如果不是要删除的结点那么两个指针分别后移,这一步该为pre=pre->next; 
            p=p->next ;   
        }   
    } 
} 
  1. 应用实例
#include<stdio.h>
#include<stdlib.h>
//主函数
int main()
{
    int arr[7]={5,4,7,3,3,9,0};         //这个是例子。 
    node*L=creatlist(arr,7),*p;
    insert(L,2,12);
    del(L,3);
    p=L->next;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next; 
    }
}

栈和队列

  1. 先进后出,后进先出
  2. 只能在一端(栈顶,Top)进行插入(入栈,Push)和删除(出栈,Pop)
  3. 基本操作
Stack CreateStack(int MaxSize); 		//生成空堆栈,其最大长度为MaxSize
int IsFull(Stack S, int MaxSize);		//判断堆栈S是否已满
viod Push(Stack S, ElementType item);	//将元素item压入堆栈S
int IsEmpty(Stack S);					//判断堆栈S是否为空
ElementType Pop(Stack S);				//删除并返回栈顶元素
  1. 栈的表示,数组及单向链表
//数组
//堆栈的定义
#define MaxSize <存储原始的最大个数>
typedef struct
{
	ElementType Data[MaxSize];
	int Top;
}Stack;

//入栈Push
void Push(Stack *S, ElementType item)
{
	if(S->Top == MaxSize - 1)
	{
		printf("error")
		return;
	}
	else
	{
		S->Data[++(S->Top)] = item;
		return;
	}
}

//出栈Pop
ElementType Pop(Stack *S)
{
	if(S->Top == -1)
	{
		printf("error");
		return ERROR;
	}
	else
		return (S->Data[(S->Top)--]);
}
//单向链表
//堆栈的定义
typedef struct Node
{
	ElementType Data;
	struct Node *Next;
}LinkStack;
LinkStack *Top;

//创建一个堆栈
LinkStack *CreateStack()
{
	LinkStack *S;
	S = (LinkStack *)malloc(sizeof(struct Node));
	S->Next = NULL;
}

//判断堆栈是否为空
int IsEmpty(LinkStack *S)
{
	return (S->Next == NULL);
}

//入栈Push
void Push(ElementType item, LinkStack *S)
{
	struct Node  *TmpCell;
	TmpCell = malloc(sizeof(struct Node));
	TmpCell->Element = item;
	TmpCell->Next = S->Next;
	S->Next = TmpCell;
}

//出栈Pop
void Pop(LinkStack *S)
{
	struct Node  *FirstCell;
	ElementType result;
	if(IsEmpty(S))
	{
		printf("error");
		return NULL;
	}
	else
	{
		FirstCell = S->Next;
		S->Next = FirstCell->Next;
		result = FirstCell->Element;
		free(FirstCell);
		return result;
	}
}

队列

  1. 先进先出(FIFO,first-in first-out),后进后出
  2. 只能在一头插入,另一端删除
  3. 基本操作
Queue CreateQueue(int MaxSize);			//生产长度为MaxSize的空队列
int IsFullQ(Queue Q, int MaxSize);		//判断队列Q是否已满
void AddQ(Queue Q, ElementType item);	//将元素插入队列Q中
int IsEmptyQ(Queue Q);					//判断队列Q是否为空
ElementType DeletQ(Queue Q);			//将队头元素从队列中删除并返回
  1. 队列的表示,数组及单向链表
//数组,单向存储
#define MaxSize <存储原始的最大个数>
typedef struct
{
	ElementType Data[MaxSize];
	int rear;
	int front;
}Queue;

//入队
void AddQ(Queue *P, ElementType item)
{
	if((P->rear+1)%MaxSize == P->front)
	{
		printf("Full");
		return;
	}
	P->rear = (P->rear + 1)%MaxSize;
	P->Data[P->rear] = item;
}

//出队
ElementType DeleteQ(Queue *P)
{
	if(P->front == P->rear)
	{
		printf("Empty");
		return ERROR;
	}
	else
	{
		P->front = (P->front+1)%MaxSize;
		return P->Data[P->front];
	}
}
//链表
typedef struct Node
{
	ElementType Data;
	struct Node *Next;
}QNode;
typedef struct
{
	QNode *rear;
	QNode *front;
}LinkQueue;
LinkQueue *P;

//出队
ElementType DeleteQ(LinkQueue *P)
{
	QNode *FrontCell;
	ElementType FrontElem;
	if(P->front == NULL)
	{
		printf("Empty");
		return ERROR;
	}
	FrontCell = P->front;
	if(P->frony == P->rear)
	{
		P->front = P->rear=NULL;
	}
	else
	{
		P->front = P->front->Next;
	}
	FrontElem = FrontCell->Data;
	free(FrontCell);
	return FrontElem;
}

定义
n(n>=0)个节点构成的有限集合(n=0则空树)

性质

  1. 子树是互不相交的
  2. 除了根节点之外,每个节点有且只有一个父节点(根节点没有父节点)
  3. 一棵树N个节点有N-1条边

基本术语

  1. 节点的度(degree):节点的子树个数(子树可以超过两个)
  2. 树的度(degree):树中所有节点中最大的度数(节点的度的最大值)
  3. 叶节点(leaf):度为0的节点
  4. 父节点(parent)
  5. 子节点(child)
  6. 兄弟节点(sibling)
  7. 路径和路径长度:路径所包含边的个数
  8. 祖先节点(ancestor):沿树根到某一节点路径上所有的节点都是
  9. 子孙节点(descendant):某一节点的子树中的所有节点
  10. 节点的层次(level):根节点1层,往下逐渐增加
  11. 树的深度(depth):树种所有节点的最大层次

树的表示
儿子-兄弟表示法

将一般树转化为二叉树

二叉树

  1. 二叉树有左右之分,而一般的树没有
  2. 二叉树节点定义
struct BinaryTreeNode
{
	int m_nValue;
	BinaryTreeNode* m_pLeft;
	BinaryTreeNode* m_pRight;
};

特殊二叉树:

  1. 斜二叉树(链表),0-1-3
  2. 完美二叉树,0-1-2-3-4-5-6,能有的全有了
  3. 完全二叉树,0-1-2-3-4-5,最后一层右边的若干个没有了
  4. 满二叉树,0-1-2-3-4,每个非叶子结点的度都是2
  5. 二叉搜索树,左子节点小于等于根节点,又子节点大于等于根节点
  6. 堆(最大堆和最小堆)
  7. 红黑树
    完美完全满二叉树

对于任意二叉树
设叶节点(度为0,即没有子树)的数量为n0,度等于1的节点的数量n1,度等于2的节点的数量为n2,则n0 = n2 + 1;
证明:n0 + n1 + n2 - 1 = 二叉树边的数量 = 0 * n0 + 1 * n1 + 2 * n2;

二叉树遍历概述
可以看根节点遍历的位置,判断是那种遍历方式

  1. 先序遍历(根节点->左子树->右子树)
  2. 中序遍历(左子树->根节点->右子树)
  3. 后序遍历(左子树->右子树->根节点)
  4. 层次遍历/宽度优先遍历(从上到下,从左到右)
//利用递归实现先序遍历
void PreOrderTraversal(BinTree BT)
{
	if(BT)
	{
		printf("%d", BT->Data);
		PreOrderTraversal(BT->Left);
		PreOrderTraversal(BT->Right);
	}
}
//利用递归实现中序遍历
void InOrderTraversal(BinTree BT)
{
	if(BT)
	{
		PreOrderTraversal(BT->Left);
		printf("%d", BT->Data);
		PreOrderTraversal(BT->Right);
	}
}
//利用递归实现后序遍历
void PostOrderTraversal(BinTree BT)
{
	if(BT)
	{
		PreOrderTraversal(BT->Left);
		PreOrderTraversal(BT->Right);
		printf("%d", BT->Data);
	}
}

二叉树存储
1.顺序存储(数组)
对于完全二叉树比较合适,对于一般二叉树需要把空缺的地方补满,浪费空间
2.链表存储

typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode
{
	ElementType Data;
	BinTree Left;
	BinTree Right;
}

图的遍历

图的表示
1.邻接矩阵
(a) G[N][N],N个顶点,编号为0~N-1,若G[i][j] = 1,则两个点之间存在边,若G[i][j] = 0,则两个点之间不连通;
(b) 直观、简单、好理解,但是如果图稀疏,则浪费空间、时间。
2.邻接表
(a) G[N]为指针数组,对应矩阵每行一个链表,只存非0元素
(b) 稀疏图合算,否则不合算
邻接表

几个定义
强联通:有向图中顶点V和W之间存在双向路径,则称V和W是强联通的
强联通图:有向图中任意两顶点均强联通
强联通分量:有向图的极大强联通子图

1.深度优先搜索(Depth First Search, DFS)
类似于树的先序遍历,如果用邻接表储存图,时间复杂度为N+E(N个顶点,E条边);如果用用邻接矩阵储存,时间复杂度为N^2。

void DFS(Vertex V)
{
	Visited[V] = true;
	for(V的每个邻接点W)
	{
		if(!Visited[W])
		{
			DFS(W);
		}
	}
}

2.广度优先搜索(Breadth First Serach, BFS)
类似于树的层序遍历,如果用邻接表储存图,时间复杂度为N+E(N个顶点,E条边);如果用用邻接矩阵储存,时间复杂度为N^2。

void BFS(Vertex V)
{
	Visited[V] = true;
	Enqueue(V,Q);
	while(!IsEmpty(Q))
	{
		V = Dequeue(Q);
		for(V的每个邻接点W)
		{
			if(!Visited[W])
			{
				Visited[W] = true;
				Enqueue(W,Q);
			}
		}
	}
}

最短路径问题
1.单源最短路径问题:从固定源点出发
a. 无权图的单源最短路径算法
按照递增(非递减)的顺序找到各个顶点的最短路
按照递增(非递减)的顺序找到各个顶点的最短路

void Unweight(Vertex  S)
{
	Enqueue(S, Q);
	while(!IsEmpty(Q))
	{
		V = Dequeue(Q);
		for(V的每个邻接点W)
		{
			if(dist[W] == -1)
			{
				dist[W] = dist[V] + 1;
				path[W] = V;
				Enqueue(W,Q);
			}
		}
	}
}

b. 有权图的单源最短路径算法
按照递增的顺序找到各个顶点的最短路
有权图的单源最短路径算法
3

//不能解决有边的权重为负数的情况
void Dijkstra(Vertex s)
{
	while(1)
	{
		V = 未收录顶点中dist最小者;
		if(这样的V不存在)
		{
		break;
		}
		collected[V] = true;
		for(V的每个邻接点W)
		{
			if(collected[W] == false)
			{
				if(dist[V]+E<v,w> < dist[W])
				{
					dist[W] =  dist[V]+E<v,w>;
					path[W] = V;
				}
			}
		}
	}
}

2.多源最短路径问题:求任意两顶点之间的最短路径
方法1:直接讲但愿最短路算法调用|V|遍,时间复杂度为O(|V|^3+|E|*|V|)
方法2:Floyd算法,时间复杂度为O(|V|^3)
4

void Floyd()
{
	for(int i=0; i<N; i++)
	{
		for(int j=0; j<N; j++)
		{
			D[i][j] = G[i][j];
			path[i][j] = -1;
		}		
	}
	for(int k=0; k<N; k++)
	{
			for(int i=0; i<N; i++)
			{
				for(int j=0; j<N; j++)
				{
					if(D[i][k]+D[k][j]<D[i][j])
					{
						D[i][j] = D[i][k]+D[k][j];
						path[i][j] = k;
					}
				}		
			}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值