一、写在前面
如果我说数据结构是计算机从业人员最基本也必须要掌握的,就像演员记台词是本分一样,应该没人会纠正我吧。下面就让我们一起来将数据结构中最基本的线性表、栈和队列搞个清清楚楚明明白白。
二、线性表
首先我们要知道线性表是具有相同特性数据元素的一个有限序列,该序列中所含元素的个数称为线性表的长度。就比如说吧,有一队士兵,士兵的人数就对应线性表的长度,士兵的人数是有限的,这就对应了有限序列,队伍中所有的都是士兵,这就对应了相同特性的数据元素。
当然,在一队士兵中,只有一个士兵在队头,一个士兵在队尾,在队头的士兵前面没有其他士兵,在队尾的士兵后面也没有其他士兵。除了在队头队尾的士兵外,其他的士兵紧挨着站在其前面和后面的士兵只有一个。线性表也是这样的,只有一个表头元素,一个表尾元素,表头元素没有前驱,表尾元素没有后继,除表头表尾元素外,其他元素都只有一个直接前驱,一个直接后继。
线性表的存储结构有顺序存储结构和链式存储结构。前者称为顺序表,后者称为链表。
(1)顺序表
顺序表就是把线性表中的所有元素按照其逻辑顺序,依次存储到从指定存储位置开始的一块连续的存储空间中。就比如说下图中几个房间都挨着,你可以根据房间号马上就找到任何一个房间的位置,这就是顺序表的随机访问特性。虽然这样方便了,不过在插入删除房间时,就可能要移动很多位置才可以达到挨着的效果,所以说顺序表在插入删除元素时候要移动多个元素。
那么就到了定义顺序表结构体的时候了,其实很简单
typedef struct{
int data[maxSize]; //存储顺序表元素的数组,类型就你随意定,这儿我定义了int型
int length; //存储顺序表的长度
}Sqlist;//顺序表类型的定义
然后下面就开始初始化顺序表,插入元素,查找元素,删除元素了
//初始化
void initList(Sqlist &L){
L.length = 0 ;//只需要把长度设为0就完事了
}
//插入
void insert(Sqlist &L,int p ,int e)//p是插入位置,e是插入元素
{
int i = 0;
if(p <0 || p>L.length || L.length == maxSize)//就位置错误,或者表已经达到最长,插入不成功,返回0
{
return 0;
}
for(i=L.length-1 ; i>=p ; --i){
L.data[i+1] = L.data[i];//从后往前,逐个移位置
}
//现在就找到插入位置了,开始插入了
L.data[p] = e;
(++L.length);//插入了一个元素,长度加一
return 1;//插入成功,返回1
}
//寻找
int find(Sqlist L, int e){
int i;
for(i=0; i > L.length;i++){
if(e == L.data[i]){
return i;//查找成功返回下标
}
}
return -1;//不成功返回-1
}
//删除
int delete(Sqlist &L,int e){
int index = find(L,e);//找到删除元素的下标
if(index == -1){
return 0;//元素不存在
}
for(i=index ; i<L.length;i++){
L.data[i] = L.data[i+1];//用后面的元素覆盖
}
--(L.length);//长度减1
return 1;//删除成功
}
(2)链表
在链式存储中,每个结点不仅包含所存元素的信息,还包含着元素之间的逻辑信息。接着以房间为例,如下图,链表房间是散落的,每个房间有指向下一个房间的箭头,要想访问房间必须从第一个房间开始,依次访问,所以链式存储方式不支持随机访问。由于链表中的每个结点还需要划出一部分空间来存储指向下一个结点位置的指针,因此链表中结点的存储空间利用率较顺序表稍低一点。链表的结点可以散落在内存中的任何位置,而且不需要一次性划分所有结点所需的空间,因此,链表支持存储空间的动态分配。还是房间这个例子,如果新加一个房间,只需要改动房间的指向箭头就可以了,所以说,链表中进行插入删除操作不需要移动元素。
链表中还有单链表、双链表、循环单链表、循环双链表和静态链表之分。
(I)单链表
链表在每个结点中除了包含数据域外,还包含一个指针域,用以指向其后继结点。如下图所示
可以看到上图有一个头结点,这儿,咱就聊一下带与不带头结点的区别:
(1)带头结点的单链表,头指针head指向头结点,头结点的数据域不包含任何信息,从头结点的后继结点开始存储数据信息。头指针始终不等于NULL,当head—>next等于NULL时,链表为空。
(2)不带头结点的单链表,头指针head始终指向开始结点,也就是图中的A1,当head等于NULL时,链表为空。
单链表的结构体定义以及操作如下(双链表等类似,不再仔细说明):
//结构体定义
typedef struct LNode{
int data;//存放数据域
struct LNode *next;//指向后继结点的指针
}LNode;
//尾插法建立链表
void create(LNode *&C , int a[] , int n ){
LNode *s,*r;//s用来指向新申请的结点,r始终指向C的终端结点
int i;
C = (LNode *)malloc (sizeof(LNode));//申请C的头结点空间
r = C;
for(i=0;i<n;i++){
s=(LNode *)malloc (sizeof(LNode));
s->data = a[i];//用新申请的结点接受a中的一个元素
r->next = s;//用r接受新结点
r = r->next;//r指向终端结点
}
r->next = NULL;//全部插入完成,终端结点置为NULL
}
//结点查找
while(p->next!=NULL){
if(p->next->data == x){
break;
}
p = p->next;
}
if(p->next == NULL){
return 0;//查找失败
}
//结点删除
q = p->next;
p->next = p->next->next;//直接用后一个覆盖
free(q);//释放内存空间
(II)双链表
双链表就是在单链表的结点上又增添了一个指针域,指向当前结点的前驱。
(III)循环单链表
循环单链表就是在单链表的基础上,让终端结点指向链表的第一个结点。
(IV)循环双链表
和循环单链表类似,循环双链表就是在双链表的基础上让终端结点的next指针指向头结点,头结点的prior指针指向终端结点。
(V)静态链表
静态链表借助一维数组来表示,下图中左边为静态链表,右边为其对应的一般链表
三、结局
反正就是不怎么难,理解就完事了。图画的实在是太丑了。