抽象数据类型(abstract data type,ADT)是一些操作的集合。
简单得讲,一个线性表就是n个数据元素的有限序列 ( a 1 , a 1 , a 2 , ⋯ , a n ) (a_1,a_1,a_2,\cdots ,a_n) (a1,a1,a2,⋯,an)。在计算机内,可以用不同的方式表示线性表,其中最简单和常用的方式是用一组地址连续的存储单元依次存储线性表的元素。另外,链式存储也是常用的存储方式,它不仅可以表示线性表,还可以表示非线性的数据结构。
表的所有操作都可以用数组实现,虽然数组是动态指定的,但是还是需要对表的大小最大值进行估计。因此,对于一些未知大小的表而言,通常会浪费大量的空间。数组实现对于打印、查找等操作而言是方便的,但是对于插入和删除操作是昂贵的,后者操作一般不用数组实现。
顺序表数据类型描述:
typedef int datatype; //假设
#difine MASXSIZE 1024 //假设
typedef struct{
datatype data[MAXSIZE];
int last;
}SequenList;
优点:
存储密度高;在结点等长时,可以随机存取表中任意元素;元素存储位置可用一个简单直观的公式来表。
缺点:插入、删除运算时,需大量移动元素;占用连续存储空间,对于长度变化较大的顺序表,需要按照最大空间分配,使存储空间利用率降低。
链表用一组任意的存储单元来存储线性元素,这组数据单元可以是连续的,也可以是不连续的。在存储每个线性元素的同时,还必须存储指示其后继元素的地址。这两部分信息,即元素data和指针next组成数据元素的存储映像,即结点。
链表的每个结点中只包含一个指针域,这种链表称为单链表。显然,单链表的每个结点的数据域地址存储在前趋结点的指针域中,而开始结点无前趋结点,并且当我们需要在第一个结点进行插入和删除操作,都将改变表的起始端。因此,我们通常设立头指针head指向开始结点,同时终端结点的指针域为空,即NULL。
单链表数据类型描述:
typedef int datatype;
typedef struct node{
datatype data;
struct node *next;
}linklist;
linklist *head,*p;
头插法建表,顾名思义,从头部插入元素建立链表,其生成的链表中结点的次序和输入顺序相反。
// 头插法,新结点插入到表头上
linklist* CreatListF(){
char ch;
linklist *head,*s;
head = NULL;
while((ch = getchar()) != '#){
s = (linklist*)malloc(sizeof(linklist));
s->data = ch;
s ->next = head;
head = s;
}
return head;
}
尾插法建表,从尾部插入元素建立链表,生成链表的结点次序和插入顺序一致。
// 尾插法,新结点插入到表尾
linklist* CreatLinstR(){
char ch;
linklist *head,*s,*r;
head = NULL;
s = NULL;
while((ch = getchar()) != '#){
s = (linklist*)malloc(sizeof(linklist));
s->data = ch;
if(head == NULL)
head = s; //空表,新结点插入表头
else
r-next = s; //非空表,新结点插入到表尾
r = s; //指针r指向新的表尾
}
if(r != NULL) r->next = NULL;
return head;
}
单链表查找元素具有单向性,在每个结点中增加一个指向直接前趋的指针,这种称为双链表。
typedef struct dnode{
datatype data;
struct dnode *prior,*next;
}dlinklist;
dlinklist *head;
最后一个结点指向头结点,因此从表中任意结点出发均可以找到表中其他结点。
栈是限定仅在表尾进行插入和删除运算的线性表。栈是一种先进后出的数据结构(FILO-First In Last Out)。
由于栈顶是固定不变的,而栈顶的位置是随出栈入栈变化的,因此需要记住栈顶的位置。
// 顺序栈数据结构定义
struct Stack {
datatype elements[MAXSIZE];
int Top;
}
// 置空
struct Stack *SetNullS(struct Stack *S){
S->Top = -1;
return S;
}
// 判空
int EmptyS(struct Stack *S){
if(S->Top == -1)
return 0;
else
return 1;
}
// 入栈
struct Stack *PushStackS(struct Stack *S,datatype e){
if(S->Top >= MAXSIZE-1){
printf("Stack Overflow!\n");
return NULL;
}
else{
S->Top ++;
S->elememts[S->Top] = e;
}
return S;
}
// 出栈
datatype *PopStackS(struct Stack *S){
datatype *ret;
if(EmptyS(S)){
printf("Stack Underflow!\n");
return NULL;
}
else{
ret = (datatype *)malloc(sizeof(datatype));
*ret = S->elements[S->Top];
S->Top --;
return ret;
}
}
栈顶是top指针,它唯一确定一个链栈,当top等于NULL时,表示栈为空栈。
//链栈数据结构定义
struct Node{
datatype element;
struct Node *next;
}
struct Node *top;
// 入栈
struct Node *PushStackL(struct Node *S,datatype e){
struct Node *p;
p = (struct Node*)malloc(sizeof(struct Node));
p->element = e;
p->next = S;
S = p;
return S;
}
// 出栈
datatype *PopStackL(struct *Node){
datatype *ret;
if(S == NULL )
}
队列是一种受限的线性表,它只允许在表的一端进行插入,而在另一边进行删除。队列是一种先进先出的数据结构(FIFO-First In First Out)。
由于队列的头和尾都是变化的,因此需要两个指针指向队头和队尾。
// 顺序队列的数据结构定义
struct sequeue {
datatype data[MAXSIZE];
int front,rear;
}
struct sequeue *sq;
指定,front指向当前队头元素的前一个位置,rear指向当前队尾的元素。
显然,入队和出队算法如下:
// 入队
sq->rear ++;
sq->data[sq->rear] = x;
// 出队
sq-> front --;
*temp = sq->data[sq->front];
当前队列中元素个数n = (sq->rear)-(sq->front)
。其中n=0,则说明队列为空,n=MAXSIZE是则表示队列满。在队列空时,继续出队操作会造成“下溢”,队满时进行入队操作,会造成“上溢”。front后移动,rear指针也继续向后移动,即不断地出队和入队,数组的总长度是一定的,当rear到达数组边界时继续入队会造成“假上溢”,即队列未满但是不可继续入队操作。“假上溢”会造成内存空间的浪费,需要经历避免。为了避免这个现象,通常采取一个环状的队列,即首尾相接的循环队列。
循环队列下的rear+1操作有些许不同:
if((sq->rear + 1) == MAXSIZE)
sq->rear =0;
else
sq->rear ++;
上述代码可以等同为sq->rear = (sq->rear + 1) % MAXSIZE
。front和rear的操作都是加1,因此代码类似sq->front = (sq->front + 1) % MAXSIZE
。
当队空时,头指针追上尾指针,队满时尾指针追上头指针,因此单靠头、尾指针是否相等已经无法判断队列情况了。只需要在尾指着加1前,先判断尾指针在加1(可以定义一个标志位tag,判断是否进行了入队操作)后在循环意义上的位置是否等于头指针的位置,即(sq->rear + 1) %MAXSIZE == sq->front
,如果相等则此步操作后队列满。
链队列是限制仅在表头删除和表尾插入的单链表。一个队列由一个头指针和尾指针唯一确定。
// 链队里的数据结构定义
typedf struct {
linklist *fornt,*rear;
}linkqueue;
linkqueue *q;
// 置队空
linkqueue *SetNullQ(linkqueue *q) {
q->front = (linklist*)malloc(sizeof(linklist));
q->front->next = NULL;
q-rear = q-front;
}
// 判队空
int EmptyQ(linkqueue *q){
if(q->front == q->rear)
return 1;
else
return 0;
}
// 取队头
datatype *FrontQ(linkqueue *q) {
datatype *ret;
if(EmptyQ(q)){
printf("queue is empty!\n");
return NULL;
}
else{
ret = (datatype *)malloc(sizeof(datatype));
*ret = q->front->next->data;
return ret;
}
}
// 入队
void EnQueueQ(linkqueue *q,datatype x) {
q->rear->next = (linklist *)malloc(sizeof(linklist)); // 新结点插入尾端
q->rear = q->rear->next; // 尾指针指向新的结点
q->rear->data =x; // 给新的结点赋值
q->rear->next =NULL;
}
// 出队
datatype *DeQueueQ(linkqueue *q) {
datatype *ret;
linklist *s;
if(EmptyQ(q)){
printf("queue is empty!\n");
return NULL;
}
else{
s = q->front->next;
if(s->next == NULL){
q->front->next = NULL;
q->rear = q->front
}
else
q->front->next = s->next;
ret = (datatype *)malloc(sizeof(datatype));
*ret = s->data;
return ret;
}
}