数据结构(C语言版)学习笔记(一)
——顺序表与链表。
先从相关名词与基本概念开始:(一)数据、数据元素、数据项、数据对象;(二)逻辑结构、存储结构;(三)顺序表及相关操作;(四)链表、单链表、双向链表、结点(数据域,指针域)、头结点、尾结点、直接前驱、直接后驱、相关操作。
基本概念:
**数据:**数据是一个抽象了之后的概念,我们把所有能输入到计算机中,并能被计算机程序处理的符号称为数据。(Data)
**数据项:**是组成数据元素、有独立含义,不可分割的最小单位。(Data Item)
**数据元素:**数据元素是数据的基本单位,数据元素用来完整的描述一个对象。(例如,对一本书的记录有书名,定价,页数等等“数据项”,通过这些数据项,组成了数据元素,用来完整的描述书这个对象。)(Data Element)
**数据对象:**性质相同的数据元素的集合,是数据的一个子集。(在这里举一个实际的例子,用于理解什么是数据对象。例如,成绩单就是一个数据对象。成绩单中每一行中的每一项就是一个数据项,每一行就是一条数据元素,成绩单中的所有行,就构成了数据对象。)(Data Object)
**逻辑结构:**数据的逻辑结构,就是从逻辑关系上描述数据。它与数据的存储无关。也就是说,数据的逻辑结构只是我们从实际中抽象出来的一种数学模型。数据的逻辑结构,关心的问题是怎么描述数据元素之间的关系。四种基本的逻辑结构:集合结构、线性结构、树结构、图结构。
存储结构:也成物理结构,其关心的是数据是怎么在计算机中(存储器中)进行存储的。一定要区分逻辑结构与存储结构。在计算机中的存储结构有两种:顺序存储结构和链式存储结构。顺序存储结构是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系。也就是说,数据元素之间的物理地址是相邻的,要求所有的元素是存放在一片连续的存储空间内。而链式存储结构则不然,链式存储结构无需占用一整块内存空间,只需要在结点上附加指针字段即可。
顺序表及相关操作:
**顺序表:**用一组地址连续的存储单元依次存放数据元素。逻辑和存储结构上都是线性相邻的。
typedef int Status; //重新定义int为Status
#define OVERFLOW 0;
#define OK 1;
//---顺序表的存储结构---
#define MAXSIZE 100
typedef struct{
ElemType *elem; //这里的ElemType只是伪代码,具体的数据元素或者数据项的类型可以自己定义。
int length;
}Sqlist;
//假如,我们需要存储的数据项就只是简单的int、float、double等类型(这就变成了数组,我们可以直接定义成数据即可),当然,这是脱离了实际的应用的,只是为举了例子。
#define MAXSIZE 100 //宏定义
typedef struct{
int *elem; //此时,这里的ElemType就是具体的int类型的指针变量。
int length;
}Sqlist;
//更复杂的数据类型,例如:
typedef struct{
char IBSN[20];
char name[50];
float price;
}book;
//我们在这里定义了一个结构体,为book类型的变量,则线性表的定义如下:
#define MAXSIZE 100
typedef struct{
book *elem; //此时,这里的ElemType是book类型的指针变量,指向book的一个指针。
int length;
}Sqlist;
//在定义了Sqlist之后
Sqlist L;
//将L定义成Sqlist类型的变量,可以通过L.elem[i-1]可以访问第i个序号的数据项。
**基本操作:**初始化、取值、查找、插入、删除。
//顺序表的初始化:
Status InitList(Sqlist &L){
L.elem = new ElemType[MAXSIZE]; //给线性表分配一个大小为MAXSIZE的内存空间,相当于是一个ElemType类型的数组。
if(!L.elem) {
exit(OVERFLOW);
}
L.length = 0;
return OK;
}
//顺序表的取值:
//我们先描述一下该算法的步骤,然后使用计算机语言进行表述。
//第一步,我们需要先判断取值是否合理,若不合理,返回ERROR。
//第二步,如果所取的值合理,我们将它赋给某参数,或者返回该值。
Status Getitem(Sqlist L, int i, ElemType &e){ //这里的&是引用,相当于传进来的是实参,这样变量的寿命就增长了。
if (i<1 || i>L.length){
return ERROR; //首先判断所要取的元素有没有“越界”。
}
e = L.elem[i-1];
return OK;
}
//顺序表的查找:
//遍历整个顺序表,找到,则赋给某参数或者返回;若未找到,返回0。
Status finditem(Sqlist L, ElemType e){
for (i=0; i<L.Length; i++){
if (L.elem[i]==e){
return i+1;
}
}
return 0;
} //查找结果返回零,代表查找失败。
//顺序表的插入:
//第一步,判断所插入的位置是否合法,若插入位置不合法,则返回ERROR;
//第二步,判断顺序表的存储空间是否已满,如果存储空间已满,返回OVERFLOW;
//第三步,将所要插入位置及其以后的元素依次向后移动,然后将元素插入;
//第四步,表长加一。
Status Insertitem(Sqlist &L, ElemType e, int i){
if ((i<1) || (i>L.Length+1)){
return ERROR;
}
if (L.Length==MAXSIZE){
return OVERFLOW;
}
for (j=L.Length; j>i; j--){
L.elem[j+1] = L.elem[j];
}
L.elem[i] = e;
++L.Length;
return Ok;
}
//顺序表的删除:
//与插入类似,有区别的是,不用判断内存空间已满;将删除元素之后的所有元素前移。
Status delitem(Sqlist &L, int i){
if ((i<1) || (i>L.Length+1)){
return ERROR;
}
for (j=i; j<=L.Length; j++){
L.elem[j-1] = L.elem[j];
}
--L.Length;
return OK;
}
链表及其基本操作:
前言:线性表链式存储结构的特点是,用一组任意的存储单元存储线性表的数据元素(存储单元可以是连续的,也可以是任意的,不连续的)。那么,如何才能将这些数据元素”连接“起来呢???因此,为了表示这种逻辑关系,就需要每个结点不仅保存有数据元素(数据域),还同时保存了该结点的直接前驱和直接后继,也就是该结点的前一个结点和后一个结点的地址(指针域)。结点保存了两个指针,所形成的链表称为双向链表。只保存了一个指针(指向下一个节点)所形成的的链表称为单链表。
根据链表结点所包含指针个数、指针指向和指针连接方式,可将链表分为单链表、循环链表、双向链表、二叉链表、十字链表、邻链表、邻接多重表等。
**链表:**先通过一张图感受一下链表。
//---单链表的存储结构---
typedef struct{
ElemType data; //结点的指针域
struct LNode *next; //结点的指针域
}LNode, *LinkList; //LinkList为指向结构体LNode的指针类型。
//注:这里其实同时为同一个结构体起了两个不同的名字;LNode*和LinkList是等价的。通常习惯上是用LinkList定义单链表的头指针;LNode*定义指向单链表中任意节点的指针变量。
//注意区分结点变量和指针变量两个不同的概念,若定义LinkList p或LNode *p,则p为指向某一节点的地址,为指针变量;而*p为结点名称,对应的是结点变量。
基本操作:初始化、取值、查找、插入、删除、链表的创建。
单链表的插入:
单链表的删除:
//单链表的初始化:
Status InitList(LinkList &L){ //传入的是一个指针变量
L = new LNode; //为该指针变量新开辟一个内存空间,LNode数据类型。
L -> next = NULL; //将该节点的指针域置空。
return OK;
}
//单链表的取值:
//从头结点开始,一直往下访问,直到找到所需查找的值。
Status finditem(LinkList L, ElemType &e, int i){
LinkList p;
p = L -> next;
j=1;
while(p&&j<i){ //p为非空指针,并且j<i。j在这里为计数器。
p = p -> next;
++j;
}
if(!p || j>i){
return ERROR; //如果p是空的或者j>i说明i值不合法。
}
e = p -> data; //把指针的数据域赋给某参数e,其实也可以直接返回 p -> data,这个要看具体的需求而定。
return OK;
}
//单链表的查找:
//按值查找,返回结点的指针:
LNode *Getitem(LinkList L, Elemtype e){
LinkList p;
p = L -> next;
while(!p && p -> data != e){ //p非空并且p的数据域不等于e,继续循环。
p -> next;
}//跳出循环则说明找到了查找的值。
return p;
}
//单链表的插入:
//先使用文字描述算法步骤:在i个结点处插入新的结点。
//第一步,找到第i-1个结点,并用指针p指向该节点;
//第二步,创建新的结点,将新结点的指针域指向第i个结点;
//第三步,将结点*p的指针指向新的结点;
Status Inseritem(LinkList &L, ElemType e, int i){
LinkList p;
p = L;
j = 0;
while(p && (j<i-1)){ //当跳出该循环时,有两种可能:一、p为空; 二、j = i - 1
p -> next;
++j;
}
if (!p || (j>i-1)){
return ERROR;
}
s = new LNode;
s -> data = e;
s -> next = p -> next;
p -> next = s;
return OK;
}
//单链表的删除:
Status delList(LinkList &L, int i){
LinkList p, q;
p = L;
j = 0;
while((p -> next) && (j<i-1)){
p -> next;
++j;
}
if (!(p -> next) || (j>i-1)){
return ERROR;
}
q = p -> next;
p -> next = q -> next;
delete q;
return OK;
}
//单链表的创建:
//前插法创建单链表:
void CreateList(LinkList &L, int n){
LinkList p;
L = new LNode;
L -> next = NULL;
for (i=0; i<n; i++){
p = new LNode;
cin >> p -> data;
p -> next = L -> next;
L -> next = p;
}
}
//后插法创建单链表:
void CreateList(LinkList &L, int n){
LinkList r, p;
L = new LNode;
L -> next = NuLL;
r = L;
for (i=0; i<n; i++){
p = new LNode;
cin >> p -> data;
p -> next = NULL;
r -> next = p;
r = p;
}
}