顺序表(顺序存储):
每个节点中只存放数据元素。优点:可随机存取,存储密度高;缺点:要求大片连续空间,改变容量较为麻烦。
单链表(链式存储):
每个结点除了存放数据元素外,还要存储指向下一个节点的指针。优点:不要求大量连续空间,可以很方便地拓展容量;缺点:不可随机存取,要耗费一定空间存放指针。
struct LNode{ //定义单链表结点类型
ElemType data; //每个结点存放一个数据元素;
struct LNode *next; //指针指向下一个节点
}
//增加一个新的节点:在内存中申请一个节点所需空间,并用指针p指向这个节点
struct LNode * p = (struct LNode *) malloc(sizeof(struct LNode));
typedef关键字——数据类型重命名
//使用该格式对数据类型进行重命名,方便使用
typedef <数据类型> <别名>
typedef struct LNode LNode; //将struct LNode重命名为了LNode
//在后续使用时不再需要带struct字段,可直接写为:
LNode * p = (LNode *) malloc(sizeof(LNode));
有比以上更简洁的重命名方式,可在定义单链表结构体时直接改变
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//上方写法相当于
struct LNode{
ElemType data;
struct LNode *next;
};
typedef struct LNode LNode; //强调这是一个结点
typedef struct LNode *LinkList; //强调这是一个单链表
强调这是一个结点 ——使用LinkList
强调这是一个单链表 ——使用LNode
要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点。
不带头结点,写代码较为麻烦。对第一个数据结点和后续数据结点的处理需要不同的代码逻辑;对空表和非空表的处理需要用不用的代码逻辑
bool InitList(LinkList &L) {
L = (LNode *) malloc(sizeof(LNode)); //分配一个头结点
if(L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
void test() {
LinkList L; //声明一个指向单链表的指针,此处并没有创建一个结点
InitList(L);
}
//判断单链表是否为空
bool Empty(LinkList L) {
if(L->next == NULL)
return true;
else
return false;
}
bool InitList(LinkList &L) {
L = NULL; //空表,暂时还没有任何结点,防止脏数据
return true;
}
void test() {
LinkList L; //声明一个指向单链表的指针,此处并没有创建一个结点
InitList(L);
}
//判断单链表是否为空
bool Empty(LinkList L) {
if(L == NULL)
return true;
else
return false;
}
ListInsert(&L, i, e): 在表L中的第i个位置上插入指定元素e。
方法1(带头结点的单链表):
bool ListInsert(LinkList &L, int i, ElemType e) {
if(i<1)
return false;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
while(p != NULL && j<i-1) { //循环找到第i-1个结点
p = p->next;
j++;
}
if(p == NULL) //i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return true; //插入成功
}
时间复杂度分析:
方法2(不带头结点的单链表):
方法与带头结点基本相同。
由于不存在“第0个”结点(头结点),因此i=1时需要特殊处理:
需要在之前的ListInsert函数中加入一段对于插入头部情况处理的代码:
if(i == 1) { //插入第1个节点的操作与其他节点操作不同
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s; //头指针指向新结点
return true;
}
ListDelete(&L, i, &e):删除表L中的第i个位置的元素,并用e返回删除元素的值。
方法1(带头结点):
GetElem(L,i):获取表L中第i个位置的元素的值。
LNode * GetElem(LinkList L, int i) {
if(i<0)
return NULL;
LNode *p; //指针p指向当前扫描的节点
int j = 0; //当前p指向的是第几个节点
p = L; //L指向头结点,头结点是第0个节点(不存数据)
while(p!=NULL && j<i) { //循环找到第i个节点
p = p->next;
j++;
}
return p;
}
LocateElem(L,e):在表L中查找具有给定关键字值的元素
LNode * LocateElem(LinkList L, ElemType e) {
LNode *p = L->next;
//从第1个节点开始查找数据域为e的节点
while(p != NULL && p->data != e)
p = p->next;
return p;
//找到后返回该节点指针,否则返回NULL
}
时间复杂度: O ( 1 ) O(1) O(1)
如果给你很多个数据元素,要把它们存到一个单链表里边,如何做?
Step 1:初始化一个单链表
Step 2:每次取一个数据元素,插入到表尾/表头(分别对应尾插法和头插法)
Step 1:初始化单链表
Step 2:设置变量length记录链表长度
Step 3: While 循环{
每次取一个数据元素e;
ListInsert(L, length+1, e) 插到尾部;
length++;
}
ListInsert
函数中的while循环,每次都从头开始之后遍历,时间复杂度为 O ( n 2 ) O(n^2) O(n2)
可使用后插操作InsertNextNode
进行时间复杂度简化
对头结点的后插操作
Step 1:初始化单链表
Step 2:While 循环{
每次取一个数据元素e;
InsertNextNode(L, length+1, e) 插到尾部;
}