一 线性表
1.基础概念
逻辑结构,具有相同数据类型的n个数据元素的有限序列,逻辑特性——除首元素都有直接前驱,除尾元素都有直接后继。
特点:元素个数有限;元素逻辑顺序性(有先后次序);数据元素都是单个元素;元素数据类型相同,占用相同大小的存储空间;抽象性,仅讨论元素逻辑关系而不考虑元素代表的内容。
总结:讨论逻辑,类似仓库顺序排序,且形式上相同,不考虑内容表示。
存储结构:
1.顺序存储——顺序表,地址连续空间,特点是逻辑顺序与物理顺序相同,其中位序从1开始,数组下标从0开始
2.链式存储——链表,包括单链表、双链表、循环单链表、循环双链表和静态链表(数组下标代替指针作用)
3.顺序表和链表对比:
顺序表 | 链表 | |
存取方式 | 顺序存取或随机存取 | 顺序存取 |
逻辑+物理结构 | 逻辑物理都连续 | 逻辑连续物理可不连续 |
查找 | 按值有序O(logn)无序O(n) 按位O(1) | 按值O(n) 按位O(n) |
插入和删除 | O(n) | 仅修改O(1),查找需O(n) |
空间分配 | 静态不好修改,动态麻烦 | 随用随申请,更灵活 |
存储 | 已知长度方便 | 未知长度方便,但存储密度低 |
运算 | 查找方便 | 插入删除方便 |
总结:顺序表适合进行数据查找,链表适合弹性扩充和增删操作
2.基本操作
InitList(&L):初始化,建立空表
Length(L):求表长,针对链表,顺序表结构体写明表长
LocateElem(L, e):按值查找
GetElem(L, i):按位查找
ListInsert(&L, i, e):插入元素(包括头插法和尾插法)
ListDelete(&L, i, &e):删除元素
PrintList(L):输出
Empty(L):判空,针对链表,顺序表可用L.length看
DestoryList(&L):销毁,针对链表,free(p)
3.具体代码表示
1.顺序表
//顺序表静态分配
#define MaxSize 10
typedef struct{
int data[MaxSize] //ElemType data[MaxSize];
int length;
}SqList;
//空间为 MaxSize*sizeof(ElemType)
//顺序表动态分配
#define InitSize 10 //初始长度
typedef struct{
SqList *data;
int MaxSize;
int length
}SqList, *ListPtr;
//分配语句——malloc
L.data = (ListPtr)malloc(sizeof(SqList) * InitSize);
顺序表初始化——InitList(&L):
//顺序表初始化————数组
void InitList(SqList &L)
{
for(int i = 0; i < MaxSize; i++)
{
L.data[i] = 0;
}
L.length = 0;
}
int main()
{
SqList L;
InitList(L);
……
return 0;
}
顺序表输出——PrintList(L):
//顺序表输出
void PrintList(L)
{
for(int i = 0; i < L.length; i++)
printf("data[%d] = %d\n" , i, L.data[i]);
}
顺序表插入——ListInsert(&L, i, e) :
//顺序表插入
bool ListInert(SqList &L, int i, int e)
{
if(i < 0|| i > L.length + 1)
return false;
if(L.length >= L.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++;
return true;
}
顺序表删除——ListDelete(&L, i, &e):
//顺序表元素删除————ListDelete(&L, i, &e), i是位序
bool ListDelete(SqList &L, int i, int &e) //也可用返回值返回e值,需将函数类型换为int
{
if(i < 1|| i > length)
return false;
e = L.data[i-1];
for(int j = i; j < L.length-1; j++)
L.data[j-1] = L.data[j];
L.length--;
return true;
}
顺序表查找:
//顺序表按位查找————GetElem(L, i)
int GetElem(SqList L, int i)
{
return L.data[i-1];
}
//顺序表按值查找————LocateElem(L, e)
{
for(int i = 0; i < length-1; i++)
if(L.data[i]==e)
return i+1;
return -1;
}
2.单链表
//单链表结构体
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//节点声明
LNode *p = (LNode*)malloce(sizeof(LNode));
//初始化空链表
bool InitList(LinkList &L)
{
L = NULL;
return true;
}
//判空
bool Empty(LinkList L)
{
if(L == NULL)
return true;
return false;
}
//求表长
int Length(LinkList L)
{
int len = 0;
LNode *p = L;
while(p->next != NULL)
{
p = p->next;
len++;
}
return len;
}
建立单链表:
//头插法建立单链表
LinkList List_HeadInsert(LinkList &L)
{
LNode *s;
int x;
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
scanf("%d", &x);
while(x!=-1){
s=(LinkList)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scant("%d", &x);
}
return L;
}
//强调单链表用LinkList
//强调节点用LNode
//尾插法建立单链表
LinkList List_TailInsert(LinkList &L)
{
int x;
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
LNode *s = (LNode *)malloc(sizeof(LNode));
LNode *p = L;
scanf("%d", &x);
while(x!=-1)
{
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L:
}
按位插入:
//在第i个位置插入元素e
//带头节点
bool LinkInsert(LinkList &L, int i, int e)
{
if(i<1)
return false;
LNode *p;
int j = 0;
p = L;
while(p!=NULL && j<i+1)
{
p = p->next;
j++;
}
if(p == NULL)
{
return false;
}
LNode *temp = (LNode *)malloce(sizeof(LNode));
temp->data = e;
temp->next = p->next;
p->next = temp;
return true;
}
//不带头节点
bool LinkInsert(LinkList &L, int i, int e)
{
if(i < 1)
return false;
if(i == 1)
{
LNode *temp = (LNode *)malloc(sizeof(LNode));
temp ->data = e;
temp ->next = L;
L = temp;
return true;
}
//其他和带头结点的一样
}
//单链表后插
bool InsertNextNode(LNode *p, int e)
{
if(p == NULL) return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//单链表前插————元素后移
bool InsertPriorNode(LNode *p, int e)
{
if(p == NULL) return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return true;
}
按位删除:
//带头结点
bool ListDelete(LinkList L, int i, int &e)
{
if(i < 1) return false;
LNode *p = L;
int j=0;
while(p != NULL && j<i-1)
{
p = p->next;
j++;
}
if(p == NULL || p->next == NULL) return false;
LNode *temp = p->next; //p是低i-1个节点,temp是要删除的节点
e = temp->data;
p->next = temp->next;
free(temp);
return true;
}
删除指定节点:
//删除指定节点p
bool ListDelete(LNode *p, int &e)
{
if(p == NULL) return false;
LNode *temp = p->next;
e = p->data;
p->data = temp ->data; //将后节点值前移后通过释放后节点达到链长-1,数据消失的效果
p->next = temp ->next;
free(temp);
return true;
}
按位查找:
//单链表按位查找
//带头结点
LNode * GetElem(LinkList L, int i)
{
if(i<0) return NULL;
LNode *p = L;
int j=0;
while(p != NULL && j<i)
{
p = p->next;
j++;
}
return p;
}
//该函数可用在插入删除操作下所有寻找节点的操作中进行代码封装(避免代码重复,简洁、易维护)
按值查找:
//单链表按值查找
//带头结点
LNode * LocateElem(LinkList L, int e)
{
LNode *p = L->next;
while(p!=NULL && p->data!=e)
p = p->next;
return p;
}
3.双链表
//双链表节点
typedef struct DNode{
ElemType data;
DNode *prior, *next;
}DNode, *DLinkList;
//双链表初始化
bool InitDLinkList(DLinkList &L)
{
L = (DNode *)malloc(sizeof(DNode));
if(L == NULL) return false;
L->prior = NULL;
L->next = NULL;
return true;
}
//判空
bool Empty(DLinkList L)
{
return (L->next == NULL) ? true : false;
}
双链表插入:
//双链表后插元素
bool InsertNextDNode(DNode *p, DNode *s)
{
if(p == NULL || s == NULL) return false;
s->next = p->next; //指针顺序不能乱,切切
if(p->next != NULL)
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
双链表删除:
//删除整个链表
void Destroy(DLinkList &L)
{
while(L->next != NULL)
DeleteNextDNode(L);
free(L);
L = NULL;
}
//删除p节点的后继节点
bool DeleteNextDNode(LNode *p)
{
if(p == NULL) return false;
DNode *temp = p->next;
if(temp == NULL) return false;
p->next = temp->next;
if(temp->next != NULL)
temp->next->prior = p;
free(temp);
return true;
}
4.循环链表
//单循环链表节点
typedef struct LNode{
ElemType data;
LNode *next
}LNode, *LinkList;
//初始化单循环链表
bool InitList(LinkList &L)
{
L = (LNode *)malloc(sizeof(LNode));
if(L == NULL) return false;
L->next = L;
return true;
}
//判空
bool Empty(LinkList L)
{
if(L->next == L) return true;
return false;
}
其他操作不再展开,但核心思想就是理清链表指针的关系,注意后指针不可随意断开,可能会引起链表丢失,对于前操作(如前插或删除前看元素),有向前指针就用指针,没有指针就将后值前移再删除后节点。
5.静态链表
#define MaxSize 10
typedef struct Node{
Elemtype data;
int next; //利用数组下标做游标,代替指针功能
}SLinkList[MaxSize];
二 课后习题(持续更新ing)
1.从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删除的元素的值。空出的位置由最后一个元素填补,若顺序表为空,则显示出错信息并退出程序。
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList
bool DeleteElem(SqList &L, int &e)
{
if(L.length == 0) return false;
int maxELem = L.data[0];
int maxIndex = 0;
if(L.data[maxIndex+1] > L.data[maxIndex] && maxIndex<L.length)
{
maxElem = L.data[maxIndex+1];
maxIndex++;
}
e = maxElem;
L.data[maxIndex] = L.data[length - 1];
length--;
return true;
}
2.设计一个高效算法,将顺序表L所有元素逆置,要求算法的空间复杂度为O(1)。
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
void TurnList(SqList &L)
{
int times = length/2;
for(int i=0; i<times; i++)
{
int temp = L.data[i];
L.data[i] = L.data[L.length-i-1];
L.data[L.length-i-1] = temp;
}
}
3.对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素。
#define MaxSize 100
typedef struct{
int data[MaxSize];
int length;
}SqList;
void DeleteElemX(SqList &L, int x)
{
int num = 0;
for(int i=0; i<n; i++)
{
if(L.data[i] == x)
{
num++;
}
data[i-num] == data[i];
}
L.length = n-num;
}
4.从有序顺序表中删除其值在给定值s与t之间(s<t)的所有元素,若s和t不合理或顺序表为空,则显示错误信息并退出运行。
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length
}SqList;
bool DeleteElemStoT(SqList &L, int s, int t) //通俗方法,但可能不会满分
{
if( s>=t || L.length <1) return false;
int num = 0;
for(int i=0; i<L.length-1; i++)
{
if(L.data[i] >= s && L.data[i]<= t)
num++;
else
L.data[i-num] = L.data[i];
}
L.length = L.length - num;
return true;
}
bool DeleteElemStoT(SqList &L, int s, int t) //正确方法,注意题目中的“有序条件”
{
int i,j;
if(s>=t || L.length = 0) return false;
for(i=0; i<L.length&&L.data[i]<s;i++); //单纯找符合小于s的第一个元素下标
if(i>=L.length) return false;
for(j=i; j<L.length&&L.data[j]<=t;j++) //找到大于t的第一个下标
for(; j<L.length; i++,j++)
L.data[i] == L.data[j];
L.length = i;
return true;
}
5.从顺序表中删除所有值在给定值s与t之间(包括s和t,s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
bool DeleteRepeatElem(SqList &L, int s, int t)
{
if(s>=t || L.length<1) return false;
int num = 0;
for(int i=0; i<L.length-1; i++)
{
if(L.data[i]>=s && L.data[i]<=t)
num++;
else
L.data[i-num] = L.data[i];
}
L.length = L.length - num;
return true;
}
6.从有序顺序表中删除所有值重复的元素,使表中所有元素的值均不同。
bool DeleteRepeatElem(SqList &L)
{
if(L.length<1) return false;
int num = 0;
for(int i=0; i<L.length-2; i++)
{
if(L.data[i] == L.data[i+1])
num++;
else
L.data[i-num] = L.data[i];
}
L.length = L.length - num;
return true;
}
7.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。
bool SumTwoList(SqList L1, SqList L2, SqList &L)
{
if(L1.length + L2.length > L.MaxSize) return false;
int i=0,j=0;
for(int k=0 ; k<L1.length+L2.length; k++)
{
if(L1.data[i]<=L2.data[j] || j = L2.length) //王道书将此处拆分了,但意义相同
L.data[k] = L1.data[i++];
else if(L2.data[j] < L1.data[i] || i = L1.length)
L.data[k] = L2.data[j++];
}
L.length = k; //循环中在k = L1.length + L2.length - 1 即最后一个元素时会进行加一操作
return true;
}
8.已知在一维数组A[m+n] 中依次存放两个线性表(a1,a2...am)和(b1,b2,...,bn)。编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2,...,bn)放在(a1,a2,...,am)前面。
typedef int DataType; //宏定义,方便根据需求更改
void Reverse(DataType A[], int left, int right, int size);
void ChangeListElem(DataType A[], int left, int right, int size)
{
//将a的线性表下标全部+n,从A[0]~A[m-1]到A[n]~A[m+n-1],b全部-m
//但因为未知两个线性表长度关系,并非单纯的一一对应置换问题,所以需另行考虑
//我们先将整个数组颠倒,再将对应部分的线性表进行内部颠倒
Reverse(A, 0, m+n-1, size);
Reverse(A, 0, n-1, size);
Reverse(A, n, m+n-1, size);
}
void Reverse(DataType A[], int left, int right, int size)
{
if(left >= right || right >= size) return false;
int mid = (left + right)/2;
for(int i=0; i<=mid-left; i++)
{
DataType temp = A[left+i];
A[left+i] = A[right-i];
A[right-i] = temp;
}
}
9.线性表(a1,a2,...,an)中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到则将其与后继元素位置相互换,若找不到,则将其插入表中并是表中元素仍递增有序。
void FindElemX(SqList &S, int x)
{
//对有序序列进行数据查找,首选二分查找
if(S.length<1) return false;
if(x<S.data[0]) //当x小于序列中所有元素时插入头部
{
for(int i=S.length - 1; i>0; i--)
S.data[i+1] = S.data[i];
S.data[0] = x;
}
else if(x>S.data[lS.length - 1]) //当x大于序列中所有元素时插入尾部
S.data[S.length] = x;
else{
int left = 0, right = S.length-1;
int mid = (left + right)/2;
while(left <= right)
{
if(S.data[mid] == x && mid != S.length -1) //当最后一个元素是x时无需交换
{
int temp = S.data[mid];
S.data[mid] = S.data[mid+1];
S.data[mid+1] = S.data[mid];
break;
}
else if(S.data[mid] > x)
{
right = mid - 1;
mid = (left + right)/2;
}
else if(S.data[mid] < x)
{
left = mid + 1;
mid = (left + right)/2;
}
if(left == right && S.data[mid] != x)
{
for(int i = length - 1; i>mid; i--)
S.data[i+1] = S.data[i];
S.data[mid] = x;
break;
}
}
}
}
//和课本不太一样,但初步检查是没什么问题的
10.【2010统考真题】设将n(n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中数据由(X0,X1,...,Xn-1)变换为(Xp,Xp+1,...,Xn-1,X0,X1,...,Xp-1)。要求:
1)给出算法的基本设计思路
2)根据设计思路,采用C或C++或JAVA语言描述算法,关键之处给出注释
3)说明算法的时间和空间复杂度
设计思路:参考第8题,将数组从ab转ba,首先令数组整体倒置,再让(Xn-1,...,Xp+1,Xp)和(Xp-1,...,X0)两部分分别进行内部倒置。
详细算法:
void Reverse(int R[], int left, int right, int ArraySize)
{
if(left<0 || right>ArraySize-1) return false;
int temp;
for(int i=0; i<(right-left+1)/2; i++)
{
temp = R[left+i];
R[left+i] = R[right-i];
R[right-1] = temp;
}
}
void Converse(int R[], int n, int p)
{
Reverse(R, 0, n-1, n);
Reverse(R, 0, p-1, p);
Reverse(R, p, n-1, n-p);
}
//和书上好像不太一样,但感觉是对的