线性表之所以被称作线性表,自然是因为其存储形式是线性的。通俗来讲,就是对于表中的每一个元素(除了第一个和最后一个元素),他们前面都有且只有一个元素(直接前驱),他们的后面也有且只有一个元素(直接后继)。
线性表具有如下特点:
顺序存储,简言之就是各个元素的逻辑顺序与其存放的物理顺序一致。在顺序表中,可以对其所有元素随机访问,也就是说,可以通过下标对表中任意元素进行访问操作。
在C语言中的一维数组就是一种顺序存储的线性表,但是顺序表不一定是一维数组。
顺序表的存储分静态存储和动态存储两类。其区别是:
首先给出顺序表的结构体。
#include
#include
const int maxN=30;
typedef int DataType;//元素的数据类型(假设为int)
typedef struct{
DataType data[maxN];//存储元素
int n;//表示当前表中的元素个数
}
顺序表L,查找里面有无元素x,返回元素x的位置。若没找到,返回-1。
int search(SeqList& L,DataType x){//&符号表示引用,传进去就不是形参,可以理解为传地址进去
for(int i = 0;i < L.n; i++){
if(L.data[i] == x) return i;
}
return -1;
}
先来看一看插入操作的实现思路,就是要把这个位置之后的元素依次向后挪(可以理解为往后站给他让个位置),然后再将新的元素插入进来。
代码实现
int Insert(SeqList& L,int i,DataType x){
//把x插入第i个位置,成功返回1,失败返回-1
if(L.n==maxN) return -1;//顺序表已满
if(i<1||i>n+1) return -1;//插入位置不合法
for (int j=L.n;j>=i;j--){
L.data[j]=L.data[j-1];
}
L.data[i-1]=x;
L.n++;
return 1;
}
思路和插入类似。我们只需要把需要删除的那个位置往后的元素一次往前挪就可以了。可以理解为排队时一个人走了,后面的人就上前把他的位置补上。
代码实现
int Remove(SeqList& L,int i,DataType& x){
//删除第i个元素,并通过x返回删除掉的元素的值
//若操作成功返回1,失败返回-1
if(!L.n)return -1;//为空
if(i<1||i>n+1) return -1;//删除位置不合法
for(int j=i;j<L.n;j++){
L.data[j-1]=L.data[j]
}
L.n--;
return 1;
}
在链表中,各元素的存储地址不再是连续的,他们的物理顺序和逻辑顺序并无关系。事实上,因为它为每个数据元素都附加了一个链接指针。通过这些指针,各个数据元素就按照逻辑顺序被勾连了起来。链表具有如下的特点:
首先,我们知道链表是由各个节点构成的,而节点实际上就是一个结构体,包含数据(data)和指向下一个节点位置的指针(next)。
#include
#include
typedef int DataType;//数据类型为DataType,这里假设为int
typedef struct node{
DataType data;
struct node* next;
}Node;
接下来写一个函数,只需要传入图4中的head,就可以创建一个只有头节点的空链表。
void initList(Node*& head) {
//初始化单链表,建立只有头节点的空链表
head = (Node*)malloc(sizeof(Node));
if (head == NULL) {
printf("内存分配错误");
exit(1);
}
head->next = NULL;
}
在这里有一点需要注意,传入的是Node*& head而不是Node* head。虽然head是一个指针,但我们要对指针本身进行改动,而不是对指针所指向的元素进行改动。因此,我们可以把Node*看成一种新的数据类型,添加引用符号&可以直接对指针本身进行修改。否则函数内部对指针本身的改动并不会影响外界。
计算链表的长度,即存储的data 的个数。
int Length(Node* head) {
Node* p = head->next;
int cnt = 0;
while (p != NULL) {
cnt++;
p = p->next;
}
return cnt;
}
清空链表,注意要把每一个清除的节点占用的内存空间释放掉。
void clearList(Node* head) {
Node* p;
while (head->next != NULL) {
p = head->next;
head->next = p->next;
free(p);
}
}
void Insert(Node* head, int i, DataType x) {
//将新元素x插入表中第i个位置
//首先找到第i-1个元素的位置
Node* p = head;
int cnt = 0;
while (cnt < i - 1) {
cnt++;
p = p->next;
}
//创建一个新的节点
Node* tmp = (Node*)malloc(sizeof(Node));
tmp->data = x;
tmp->next = p->next;
p->next = tmp;
}
void Remove(Node* head, int i, DataType& x) {
//删去第i个位置的节点,并将data通过x返回出来
//首先找到第i-1个元素的位置
Node* p = head;
int cnt = 0;
while (cnt < i - 1) {
cnt++;
p = p->next;
}
//重新拉链
Node* tmp = p->next;
p->next = tmp->next;
x = tmp->data;
free(tmp);
}
本文并没有详细介绍链表具体如何存入数据。因为存入数据的的操作会根据实际的情况而有很大的差异。
但链表的思想是相通的。当需要存入新的元素是,就向系统要一小块空间创建一个新的Node,然后把数据存进去。最后将这个新的Node通过相邻节点的next指针链接上去。
循环链表顾名思义就是有个循环呗。我们知道,通常单向链表的结尾的指针域为NULL,代表链表结束了。但在循环链表中,最后一个不是NULL,而是指向前面的节点(不一定要是第一个节点)。下图是一个示例:
双向链表相比于单链表,又多了一个指针域,允许每一个节点指向它的后一个元素,也允许每一个节点指向它的前一个元素。
#include
#include
typedef int DataType;//数据类型为DataType,这里假设为int
typedef struct node{
DataType data;
struct node* next;
struct node* pre;
}Node;
顺序存储的操作比起链式存储来说,确实方便了很多。比如在C语言有现成的数据类型——数组,而指针真令人头秃。。。