数据结构入门指南(C语言版)

242 阅读9分钟

初识数据结构

1.数组

数组是数据结构的基础。

数组在程序中往往是从内存整体中分配出一块连续的空间,数组反映了内存的物理结构

在这里插入图片描述

2.数组的应用

以数组为基础的数据结构,可供各种各样的算法处理大量数据

3.数据结构概念

内存的物理结构无法改变,而数据结构可以通过程序在逻辑上改变内存的物理结构,使数据按照自己的相反分布

典型的数据结构如下: 在这里插入图片描述

栈的实现方法

1.栈的特点

栈中数据的使用顺序和堆积顺序是相反的,堆积顺序是从下到上,而使用顺序是从上到上,就好像干草堆一样

在这里插入图片描述

这种数据存取方式称为LIFO(last in first out,后进先出),即最后存入的数据最先被处理

2.栈的实现

#include <stdio.h>
// 构建数组作为栈的本体 
char Stack[100];
// 栈顶指针,始终指向栈数据的最顶端 
char StackPointer = 0; 

// 入栈函数,存储数据 
void Push(char Data){
	// 数据存储在栈顶指针指向位置 
	Stack[StackPointer] = Data;
	
	// 更新栈顶指针的值 
	StackPointer++;
}

// 出栈函数,读取数据 
char Pop(){
	
	// 更新栈顶指针的值 
	StackPointer--;
	// 在栈顶指针处取出数据 
	return Stack[StackPointer];
}
 
// 运行实例 
int main()
{	
	//存储数据 
	Push(1);
	Push(2);
	Push(3);
	Push(4);
	
	//读取数据 
	while (StackPointer !=0) {
		char result = Pop();
		printf("%d\n",result);
}

}

3.原理图

在这里插入图片描述

注意此图的栈底放在上面,最底部才是栈顶

4.语法解释

  • 最终实现效果是:存入顺序是1,2,3,4;取出顺序是4,3,2,1
  • 栈的成分:数组,栈顶指针,入栈函数,出栈函数
  • 入栈函数将数据压入栈中
  • 出栈函数将数据从栈中弹出
  • 存储5个数据,最后栈顶指针指向5的地址(地址4为最后一个数据),所以在出栈函数中,栈顶指针需要减1,才能取得第一个数据

队列的实现方法

1.队列的特点

队列中最先存入的数据是被最先处理的,这种方式被称为FIFO(first in first out, 先进先出)。就像排队上车一样,先到的人就能先上车 在这里插入图片描述

2.队列的实现

#include<stdio.h> 

// 构建作为队列本质的数组 
char  Queue[100];
// 标识数据存储位置的索引 
char SetIndex = 0;
// 标识数据读取位置的索引 
char GetIndex = 0;

 // 存储数据函数 
 void Set(char Data){
 	// 存入数据
	 Queue [SetIndex] = Data;
	 
	 // 更新存储索引 
	 SetIndex++;
	  
	 // 到达数组末尾则折回开头 
	 if(SetIndex>=100){
	 	SetIndex = 0;
	 } 
 }
 
 // 读取数据函数 
 char Get(){
 	char Data;
 	
	 // 读取数据 
 	Data = Queue[GetIndex];
 	
 	// 更新读取索引 
 	GetIndex++;
 	
 	//  到达数组末尾则折回开头 
 	if(GetIndex>=100){
 		GetIndex = 0;
	 }
	
	// 返回读出数据 
	return Data;
 }
 
 
 // 运行实例 
int main()
{	
	//存储数据 
	Set(1);
	Set(2);
	Set(3);
	Set(4);
	
	//读取数据 
	while (GetIndex != SetIndex) {
		char result = Get();
		printf("%d\n",result);
}

}

3.原理图

在这里插入图片描述

4.语法解释

  • 最终实现效果是:存入顺序是1,2,3,4;取出顺序是1,2,3,4
  • 栈的成分:数组,数据存储指针,数据读取指针,存储函数,读取函数
  • 队列的逻辑结构实际上是圆环,数据存满后又会回到开头开始存数据
  • 数据读取指针和数据存储指针是一样的,走向一样,最终值(指存完数据和读完数据的最后值的值)也要相等

结构体

1.组成

结构体即把若干个数据项汇集到一起并赋予其名字的一个整体

定义完结构体后,我们可以把结构体当作一个数据类型,可以用它来声明变量

每一个被汇集到结构体的每一个数据项叫做结构体的成员

2.运用

我们需要用到结构体数组来实现链表和二叉树

3.内存分布

在这里插入图片描述

链表的实现方法

1.链表的特点

链表容易实现数据的插入和删除,任意改变数据的排列方式。就像人手拉手排成一排,要改变顺序,只需要改变牵手对象即可实现

在这里插入图片描述

2.链表的实现

参考文章:www.geeksforgeeks.org/linked-list…

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

//链表操作函数声明
void printList(struct Node* n); 
void insertAfter(struct Node* prev_node, int new_data);
void push(struct Node** head_ref, int new_data);
void append (struct Node**head_ref, int new_data);
void deleteNode(struct Node**head, int key) ;



// 创建链表节点
struct Node{
	// 存储该节点内容 
	int data;

	//  存储下一个节点的地址 
	struct Node* next;
}; 

// 程序简单创建一个三节点链表
int main(){
	// 声明节点 
	struct Node* head = NULL;
	struct Node* second = NULL;
	struct Node* third = NULL;
	
	// 为三个节点分配空间
	head = (struct Node*)malloc(sizeof(struct Node)); 
	second = (struct Node*)malloc(sizeof(struct Node)); 
	third = (struct Node*)malloc(sizeof(struct Node)); 
	
	
	// 在节点内存入数据(内容+下个节点的地址)构成链表
	head -> data = 1;
	head -> next = second; 
	
	second -> data = 2;
	second -> next = third;
	
	third -> data = 3;
	third -> next = NULL;
	
	// 最末端插入6,则链表为 1->2-> 3->6->NULL
	append(&head,6);
	
	// 在最前端插入7, 则链表为 7->1->2-> 3->6->NULL
	push(&head,7);
	 
	// 在指定位置(第三个节点的下个节点后面)插入8, 则链表为 7->1->8->2->3->6->NULL
	insertAfter(head->next,8);
	
	// 删除2
	deleteNode(&head, 2); 
	 
	printList(head); 
	
	return 0;
} 



// 从指定位置开始遍历链表函数
void printList(struct Node* n){
	// 链表的末尾一定指向NULL 
	while(n != NULL){
		printf("%d\n", n->data);
		n = n->next;
	}
}
 
// 链表插入有三种形式:1. 在最前面插入 2.指定位置插入  3. 在最末尾插入

// 1.在最前面插入
// 两个参数分别的含义是: 给定头的引用(指向指针的指针),插入的数据 
void push(struct Node** head_ref, int new_data){
	
	// (1)为新节点分配空间 
	struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
	
	// (2)放入数据
	new_node->data = new_data; 
	
	// (3) 新节点存储原头部的地址
	new_node->next = (*head_ref);
	
	// (4) 移动头部指向新节点,新节点成为新头部
	(*head_ref) = new_node; 
	
	
} 

// 2.在指定节点后面插入
void insertAfter(struct Node* prev_node, int new_data){
	
	// (1)检查给定节点是否为空
	if(prev_node==NULL){
		printf("error");
		return;
	} 
	
	// (2)为新节点分配空间 
	struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));
	
	// (3)放入数据
	new_node->data = new_data;
	
	// (4)新节点存储插入节点存储的下个节点的地址
	new_node->next = prev_node->next;
	
	// 插入节点存储新节点的地址
	prev_node->next = new_node; 
	
	
} 
 
 
// 3.在最末尾插入
void append (struct Node**head_ref, int new_data){
	
	//	(1) 为新节点分配空间 
	struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
	// 第5步中使用 ,让第五步的找尾部从头部开始 
	struct Node *last = *head_ref;
	
	// (2) 放入数据
	new_node->data = new_data;
	
	// (3)  新节点要放到最后,所以存储地址为NULL
	new_node->next = NULL; 
	
	// (4) 如果链表为空,则新节点成为头部
	if(*head_ref == NULL){
		
		*head_ref = new_node;
		return;
	
	}
	
	// (5) 链表不为空,一直摸到链表末端
	while(last->next != NULL){
		last = last->next;
	} 
	
	// (6) 原末端节点存储的地址改为新节点
	last->next = new_node;
	return; 
	 
} 


// 删除给定值所在节点 
void deleteNode(struct Node**head_ref, int key) {
 
 // 存储头部节点
 struct Node *temp = *head_ref, *prev;
  
// 如果头部节点含有给定值,需要删除头部
if(temp != NULL && temp->data == key){
	// 改变头部
	*head_ref = temp->next; 
	
	// 释放旧头部
	free(temp);
	
	return; 
} 

// 遍历节点,搜索给定值的位置  
while(temp != NULL && temp->data !=key){
	prev = temp;
	temp = temp->next;
}

// 如果给定值不存在
if(temp == NULL){
	return;
} 

// 找到位置后,开始删除操作
// 移动删除节点前一个节点的链接到删除节点的下一个节点
prev->next = temp->next;

// 释放需要删除的节点
free(temp); 

}

3.原理图

(1)链表结构图 在这里插入图片描述

(2)头部插入示意图 在这里插入图片描述

(3)指定位置插入示意图 在这里插入图片描述

(4)末端插入示意图 在这里插入图片描述

(5)删除示意图 在这里插入图片描述

4.语法解释

  • struct Node* next声明后,next存储地址,*next是地址中的值(自我引用结构体)

  • 声明节点中,struct Node* head = NULL,则head内为地址

  • malloc()函数的声明方法为:void *malloc(size_t size),其作用是分配所需的内存空间,返回值即为指向被分配内存的指针(地址)

  • 则有head,second,third存储的是指向该节点的指针(地址),要使指向该节点的指针访问到节点的成员,那就用->运算符

  • struct Node** head_ref相当于指向该结构体的指针的指针,即该指针存放的位置,相当于head(头部指针)取址即&head;* head_ref则为该结构的指针(即head,但是*head_ref这种方式才能动态移动指针)

二叉树的实现方法

1.二叉树的特点

二叉树是基于链表的,用到的还是自我引用的结构体,但是会带有两个连接信息(即指向其他元素的指针)

二叉树多用于实现用于搜索数据的算法(如:二分查找法)

二叉树结构在搜索数据时,不是沿着一条线搜索,而是循着二叉树的分叉不断向下搜索

在这里插入图片描述

2.二叉树的实现

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

struct node{
	int data;
	struct node* left;
	struct node* right;
};

// 声明操作二叉树的函数
void printPostorder(struct node* node); 
void printInorder(struct node* node);
void printPreorder(struct node* node);




// 创建一个新节点函数
// 返回值为该节点的地址 
struct node* newNode(int data){
	
	// 分配空间给新节点 
	struct node* node = (struct node*)malloc(sizeof(struct node));
	
	// 分配数据给该节点
	node->data = data;
	 
	// 初始左右分叉的指向
	node->left = NULL;
	node->right = NULL;
	
	return (node);   
		
}

int main(){
	
	// 创建二叉树的首节点(root)
	struct node* root = newNode(1);
	
	// 从首节点出发分叉出两页 
	root->left = newNode(2);
	root->right = newNode(3); 
	
	root->left->left = newNode(4);
	root->left->right = newNode(5);
	
	printf("后序顺序打印\n");
	printPostorder(root); 
	printf("\n") ;
	printf("中序顺序打印\n");
	printInorder(root); 
	printf("\n") ;
	printf("前序顺序打印\n");
	printPreorder(root); 
	
	
}

// 3种遍历方法 
// 1.后序遍历 (左->右->根) 
void printPostorder(struct node* node){
	
	if(node==NULL){
		return;
	}
	
	printPostorder(node->left);
	printPostorder(node->right); 
	
	// 打印出该节点的数据 
	printf("%d", node->data); 
} 

// 2.中序遍历(左->根->右)
void printInorder(struct node* node){
	
	if(node==NULL){
		return;
	}
	
	printInorder(node->left);
	printf("%d", node->data);
	printInorder(node->right);
	
	
} 

//  3. 前序遍历 (根->左->右)
 void printPreorder(struct node* node){
 	
	if(node == NULL){
 		return;
	 }
	 
	printf("%d", node->data);
	printPreorder(node->left);
	printPreorder(node->right);
 	
 }
 

3.二叉树原理图

(1)遍历方法示意图 在这里插入图片描述