数据结构课程学习记录。
线性表的九种基本操作:
InitList(&L):初始化表。构造一个空的线性表
DestoryList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
LocateElem(&L):按值查找操作。在表中L查找具有给定关键字值的元素。
GetElem(L,i):按为查找操作。获取列表L中第i个位置的元素的值。
ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。(前插)
ListDelete(&L,i,&e):删除操作。删除列表L中第i个位置的元素,并用e返回删除元素的值。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若L为空表,咋返回TURE,否则返回FALSE。
Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
顺序表的定义
线性表的顺序存储又称顺序表。逻辑顺序与物理顺序相同。
数组静态分配
#define MxaSize =50 //数组大小为50
typedef struct{
ElemType data[MaxSize]; //存放顺序表的元素
int length; //顺序表的长度
}SqList;
数组动态分配
#define MaxSize 50
typedef struct{
ElemType *data;
int length;
}SqList
*表示指针:指针是存放一个存储单元的地址的。
顺序表根据第一个数据元素的地址和数据元素的大小,我们可以计算出任意数据元素的位置。
动态分配语句
c L.data = (Elemtype*)malloc(sizeof(ElemType)*InitSize);
//L表示顺序表
//使用1malloc函数申请动态空间
//(使用sizeof语句计算每一个数据类型的大小)乘以(个数) = 整个需要申请空间的大小
//括号部分(Elemtype*)是强调申请空间的数据类型
c++ L.data = new ElemType[InitSize];
在使用顺序表的动态分配时,必须先使用动态分配函数分配空间才能使用,否则只有地址。
顺序表的基本操作
插入操作
//布尔类型,插入成功返回ture,失败返回false
//参数表,引用类型的顺序表L——&L;
// 整型变量i表示的是插入的位置(前插),i对应的是顺序表的标号而非是数组的下标;
// 所插入数据表的元素e
bool ListInsert(SqList &L, int i, ElemType e){
if(i < 1 || i>L.length+1)
return false;
if(L.length >= MaxSize) //查看数组是否已经满了
return false;
for(int j = L.length; j >= i; j--)
L.data[j]=L.data[j-1];
L.data[i-1] = e;
L.length++;
reeturn ture;
}
删除操作
bool ListInsert(SqList &L, int i, ElemType e){
if(i < 1 || i>L.length)
return false;
e = L.data[i-1] // 保存想要删除的元素
for(int j = i; j < L.length; j++)
L.data[j-1]=L.data[j]; // 将当前位置的值向前一位赋值
L.length--; //表长比以前少一位
return ture;
}
按值查找
//返回的是数据表的下标,所以是int
int LocateElem(SqList L, ElemType e){
int i;
for(i = 0; i < L.length; i++)
if(L.data[i] == e) //判断是否与查找相等
return i+1; //返回的是顺序表的标号,要加一
return 0;
}
线性表的链式表示
线性表的链式存储又称单链表
单链表的数据元素在存储空间上不一定相邻,所以不能实现随机存取,只能实现顺序存取,由于不相邻,我们只能通过指针来维护逻辑关系。
单链表的基本操作
头插法建立单链表
//返回值类型为单列表类型的变量LinkList
//传入的参数是单列表引用行变量L
// 当我们对传入的参数进行修改时,需传入引用型变量
LinkList List_HeadInsert (LinkList &L){
//节点指针类型的变量s,代表当前插入的节点
//整形变量X,表示所要插入的数据(可以为任意类型)
LNode *s; int x;
//初始头节点的过程
//使用malloc函数分配空间,分配了一个节点大小的空间,用sizeof求一个节点所需要的空间
//接着将next指针置为空
L=(LinkList)malloc(sizeof(Lnode));
L->next=NULL;
//头插法建立的过程
///输入数据x
scanf("%d", &x);
///循环建立
while(x!=9999){
初始化新节点的过程
s=(LNode*)malloc(sizeof(LNode))
将数据部分赋值为x
s->data=x;
修改指针的过程
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
时间复杂度为O(n)
尾插法建立单链表
LinkList List_TailInsert (LinkList &L){
int x;
L=(LinkList)malloc(sizeof(LNode));
LNode *s, *r=L;
scanf("%d", &x)
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode))
s->data=x;
//
r->next=s;
r=s;
//
scanf("%d", &x);
}
r->next=NULL;
return L;
}
按序号查找&按值查找
//按序号查找
LNode *GetElem(LinkList L,int i){
int j=1;//表示当前节点的序号
LNode *p=L->next;//指明了当前所查找的节点,初始化为头节点的下一个节点
//判断是否合法
if(i==0)
return L;
if(i<1);
return NULL;
//循环遍历单链表查找
while(p&&j<i){
//循环条件:1.当前节点一定不为空,若为空说明整个单链表已经遍历完成了也未找到当前序号所对应的节点
// 2.j<i判断当前的遍历是否有遍历到对应的序号
p=p->next;
j++;
}
return p;//返回节点p
}
//按值查找
LNode *LocateElem(LinkList L, ElemType e){
LNode *p=L->next;
while(p!=NULL&&p->data!=e)
p=p->next;
return p;
}
时间复杂度均为O(n)
插入节点操作
一、想在单链表中的第i号节点进行插入操作
(1)按序号查找到方式查找到节点的位置p=GetElem(L,i-1);
(2)修改指针,将新节点的
n
e
x
t
next
next指针指向第i号节点的位置s->next=p->next;
,接着将第i-1号节点的next指针指向新的节点p->next=s;
二、没有头结点的单链表进行头插入
(1)将新加入节点的next指针指向了原先的头指针你所指向的节点(第一个节点)s->next=head;
(2)将头指针指向新加入的节点head=s;
删除节点
想要删除单链表中的第i号节点,通过修改指针的方式
(1)找到i-1号节点的位置 p=GetElem(L,i-1);
(2)用一个额外的指针q来保存所删除的节点q=p->next;
(3)修改i-1号的next指针,将它指向i+1号节点的位置p->next=q->next;
(4)释放所删除的节点free(q);
特殊链表
双链表
节点结构为
prior |data|next
typedef struct DNode{
ElemType data;
struct DNode *prior, *next;
}DNode, *DLinklist
插入操作:想要插入第i个节点
(1)先将新节点指向后继节点指针s->next=p->next;
(2)再将第i+1个节点的指针指向为新的节点p->next->prior=s;
(3)再将新节点指向前驱节指针修改为第i个节点s->prior=p
(4)将第i个节点指向后继的指针指向新节点p->next=s;
删除操作:想要删除第i个节点
找到第i个节点的前驱节点的指针,设为q
(1)将第i-1个节点指向后继节点的指针指向了第i+1个节点p->next=q->next;
(2)将第i+1个节点指向前驱节点的指针指向了第i-1个节点q->next->prior=p;
(3)释放索要删除的节点free(q);
静态链表
#define MaxSize=50//定义存放静态链表数组的最大值
typedef struct DNode{
ElemType data;
int next;
}SLinkList[MaxSize];
用数组来实现链式存储结构的思想
顺序表VS链表
存取方式
顺序表可以实现顺序存取和随机存取
单链表只能实现顺序存取
逻辑结构和物理结构
顺序表逻辑相邻物理上也相邻,通过相邻表示逻辑关系
单链表逻辑相邻物理上不一定相邻,通过指针表示逻辑关系
查找操作
在按值查找中单链表和顺序表(无序)都为O(n)。
在按序查找中,单链表为O(n),顺序表为O(1)。
内存空间
顺序存储无论是静态分配还是非静态都需要预先分配合适的内存空间。
链式存储在需要时分配节点空间即可,高效方便,但指针要使用额外空间。
三种常用操作
最值
L=(3,1,5,9,2)
max=9;min=1
#顺序表中求最值
int min = L[0];
int max = L[0];
for(int i = 0; i < n; i++){
if(min > L[0])
min = L[0];
if(max < L[0])
max = L[0];
} //时间复杂度为O(n)
#链表中求最值
int min = p->next->data; //初始化为第一个节点当中的数据元素
int max = p->next->data;
for(; p != NULL; p=p->next){
if(min > p->data)
min = p->data;
if(max < p->data)
max = p->data;
} //时间复杂度为O(n)
逆置
L=(3,1,5,9,2)
LR=(2,9,5,1,3)
#顺序表中求逆置
int i = 0;
int j = n-1;
while(i < j){
temp = L[i]; //兑换两个数据
L[i] = L[j];
L[j] = temp;
} //时间复杂度为O(n)
#单链表中实现逆置操作
//r为最后一个节点指针
while(p->next != r){
temp = p->next; //拿出第一个节点元素并保存
p->next = temp ->next;
temp->next = r->next;
r->next = temp;
}
归并
L1=(1,2,3,5);L2=(4,6,7,8)均为升序
L=(1,2,3,4,5,6,7,8)
#
int i = 0, j = 0; //指向两个数组的标记
int k = 0; //新的辅助数组的标记
for (; i<L1_Size && j < L2_Size;k++){
if(L1[i] < L2[j])
L[k] = L1[i++];
else
L[k] = L2[j++];
while(i < L1_Size)
L[k++] = L1[i++];
while(j < L2_Size)
L[k++] = L1_Size[j++]
} //时间复杂度为O(n)
while(p->next != NULL && q->next != NULL){
if(p->nxt->data < q->nxt->data){
r->next = p->next;
p->next = p->next->next;
r = r->next;
}
eles{
r->next = q->next;
q->nxt = q->next->next;
r = r->next;
}
}
if(p->next != NULL) r->next = p->nxt;
if(q->next != NULL) r->next = q->next;
free(p);
free(q);
//时间复杂度为O(n)