计算机科学家Nikiklaus Wirth曾提出“数据结构+算法=程序”的著名公式,数据结构的重要性不言而喻。之所以数据结构如此重要,就是因为不同的数据结构采用的处理方法不同,计算复杂度也往往不同。所以良好的算法必须依赖于某种合适的数据结构,才能够发挥最大的效用。
我们常用的数据结构主要有以下几种:数组(Array)、栈(Stack),队列(Queue)、链表(Linked List)、树(Tree)、图(Graph)、堆(Heap)和散列(Hash)。按照数据的逻辑结构对其进行简单的分类,可以分为线性(栈、队列等)和非线性(树、图等)结构两类。
链表结构:
顾名思义,就是把各个数据点串联起来形成一个链条一样的结构,典型的链表结构包括数据部分和地址部分。
数据部分:保存该节点的实际数据
地址部分:保存下一节点的地址
进行链表结构操作是时,必须知道链表的“头指针”,因此我们在创建链表时也要首先定义一个“头指针”,头指针指向第一个节点的地址,第一个节点的地址部分又指向第二个节点,以此类推,最后一个节点的地址部分应该存放一个空地址NULL。以将数组转化为链表为例、基于C的链表的一些操作代码如下:
#include<stdio.h>
#include<stdlib.h>
const int maxn=10;
struct node
{
int data; //数据部分
node* next; //指向下一个节点的地址
};
void insert(node* &head,int data) //插入节点函数
{
node *head_temp=new node; //分配临时
if(head==NULL) //头节点为NULL
{
head_temp->data=data; //创建第一个节点数据
head_temp->next=NULL; //节点的下一个地址设置为NULL
head=head_temp; //将头节点的地址指向第一个节点
return;
}
else
{
node *next=new node; //分配临时节点内存
head_temp=head;
while(head_temp->next!=NULL)
{
head_temp=head_temp->next;
}
next->data=data;
next->next=NULL;
head_temp->next=next; //将链表尾节点的next地址设置为新节点的地址
}
return;
}
void Creat(node* &head,int *data) //创建链表函数
{
for(int i = 0;i < 10;i++)
{
insert(head,data[i]); //调用insert函数
}
return;
}
int main()
{
node *head=new node; //分配头结点内存
head=NULL; //头节点内存设置为NULL
int data[]={1,2,3,4,5,6,7,8,9,0}; //设置测试数组
Creat(head,data); //调用创建链表函数
node *head_t=new node; //分配临时节点内存
head_t=head;
while(head_t!=NULL) //将每个节点的与元素打印
{
printf("%d ",head_t->data);
head_t=head_t->next;
}
}
栈结构:
栈结构的特点可以概括为“先进的后出,后进的先出”。生活中很多栈结构的例子。例如:如果一个仓库只有一个门,当仓库中退房货物时,最先进来的货物只能堆放到最里面,后来的货放在外面;而要取出货物,总是先取出最外面的。
栈结构的基本操作只有两个
入栈
出栈
同样以一组数组为例进行栈的基本操作,代码如下:
#include<stdio.h>
#include<stdlib.h>
const int maxn=10;
struct DATA //定义结构体
{
int num;
};
struct Stack
{
DATA data[maxn]; //栈内元素为结构体DATA
int top; //栈顶指针
};
Stack *init() //初始化栈
{
Stack *p;
if(p=(Stack *)malloc(sizeof(Stack)));
{
p->top=0;
return p;
}
}
void PushStack(Stack* &s,int data) //入栈
{
if(s->top>maxn-1)
{
printf("溢出\n");
}
s->data[s->top++].num=data; //栈顶指针上移
}
DATA PopStack(Stack* &s) //出栈
{
if(s->top<0)
{
printf("栈为空\n");
exit(0);
}
s->top--; //栈顶指针下移
return s->data[s->top];
}
DATA GetStackElement(Stack *s)
{
if(s->top<0)
{
printf("栈为空\n");
exit(0);
}
return s->data[s->top-1];
}
int main()
{
int data[]={1,2,3,4,5,6,7,8,9,0};
Stack *s;
s=init(); //初始化
for(int i=0;i<10;i++)
{
PushStack(s,data[i]); //将数组元素入栈
}
while(s->top>0)
{
printf("*%d\n",GetStackElement(s).num); //获取栈顶元素
PopStack(s); //栈顶元素弹出
}
}
队列结构:
队列是源于生活的一种数据结构,如果说栈结构只有一个进口,那么队列结构有一个进口和一个出口。相比于栈结构的“先进的后出,后进的先出”,队列的特征则是“先进的先出,后进的后出”。一般队列的操作只有两个:
入队和出队,同样以一维数组为例进行队列的基本操作:
#include<stdio.h>
#include<stdlib.h>
const int maxn=10;
struct DATA //定义结构体
{
int num;
};
struct queue
{
DATA data[maxn]; //队列内元素为结构体DATA
int head; //队列头
int tail; //队列尾
};
queue *init() //初始化队列
{
queue *q;
if(q=(queue *)malloc(sizeof(queue)));
{
q->head=0; //初始化队列为空 头尾均为0
q->tail=0;
return q;
}
}
void InQueue(queue* &q,int data) //入队列
{
if(q->tail==maxn)
{
printf("队列满了\n");
}
else
{
q->data[q->tail++].num=data; //尾++
}
}
DATA OutQueue(queue* &q)
{
if(q->head==q->tail)
{
printf("队列已空\n");
}
else
{
return q->data[q->head++]; //头++
}
}
DATA GetQueueElement(queue *q)
{
if(q->head==q->tail) //头尾相等时 队列为空
{
printf("队列已空\n");
}
else
{
return q->data[q->head];
}
}
int main()
{
int data[]={1,2,3,4,5,6,7,8,9,0};
queue *q;
q=init(); //初始化
for(int i=0;i<10;i++)
{
InQueue(q,data[i]); //将数组的元素按照顺序入队
}
while(q->head!=q->tail) //访问队列的元素
{
printf("%d ",OutQueue(q).num);
}
}
关于队列和栈,在C++标准库中均有对应的库文件进行相关操作,如果要用到可以直接调用库文件进行操作即可,非常方便,但是在初学的时候还是尽量可以自己用C语言写出来,知其然,还要知其所以然才能够走的更远。
明天更一下树结构和图结构的操作。