顺序表
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<math.h>
/**************************************************
顺序表: 逻辑结构和存储结构都是顺序的所以叫顺序表
从索引位置插入,从索引位置弹出,并不用按顺序来就跟给数组赋值一样
特点:一个指向堆中结构体的指针(堆中的结构体指向堆中的数组,并且含有数组的大小和已经存储的数据数量)
在栈中就一个指针
最开始都是从下表为0的位置开始存储的
作用:
**************************************************/
#define swap(a, b) \
{ __typeof(a) __temp = a; \
a = b; \
b = __temp; \
}
/***************结构体定义********************/
typedef struct shunxu_table //堆中的结构体用来指向堆中的数组,
{
int *data; //指向堆中数组首元素的指针
int size, length; //数组的大小和已经存储数据数量
}sx_table;
/***************数据结构操作定义********************/
sx_table* init_sx_table(int n) //在堆中开辟结构体内存,开辟数组内存,需要参数传入数组大小
{
sx_table *p = (sx_table *)malloc(sizeof(sx_table)); //开辟结构体
p->data = (int *)malloc(sizeof(int) * n); //开辟数组
p->size = n; //保存数组大小
p->length = 0; //初始化数据数量
return p;
}
int earse(sx_table *l,int ind) //删除顺序表l的数组在ind处的数据,ind从0开始
{
if(l == NULL)
return 0;
if(ind < 0 || ind >= l->length) //当要擦除的数据在已有数据的边界外时,擦不了,整个边界包括等于号
return 0;
for (int i = ind; i < l->size; i++) //从前往后移动,将后一个数据赋值给前一个,填补空缺出来的位置
{
l->data[i] = l->data[i + 1];
}
l->length -= 1;
return 1;
}
void clear(sx_table *l) //先去掉数组内存,再去掉结构体内存
{
free(l->data);
free(l);
return;
}
int expand(sx_table *l)
{
if(l == NULL)
return 0;
int extra_size = l->size;
int *p;
while (extra_size)
{
p = (int *)realloc(l->data, sizeof(int) * (extra_size + l->size));//在原有的内存位置扩展,所有不需要数据转移
if(p) //扩展成功 ,更新结构体里的变量
{
l->size += extra_size;
l->data = p;
return 1;
}
extra_size = extra_size >> 1;
}
return 0; //扩展失败
}
int insert(sx_table *l,int ind,int val) //在l表的ind这个位置插入val大小的数据
{
if(l == NULL)
return 0;
if(ind < 0 || ind > l->length) //直接插入到已有数据的边界外是不允许的
return 0;
if(l->length == l->size) //当已经存储的数据数量等于数组大小时,要扩展数组大小
{
if(!expand(l)) return 0; //扩展失败退出程序
}
for (int i = l->size; i > ind;i--) //从后开始把前一位的数据移到后一位,将被插入的那一位空出来
{
l->data[i] = l->data[i - 1];
}
l->data[ind] = val; //在空出来的那一位上插入数据
l->length += 1; //更新结构体存储数据数量
return 1;
}
void out(sx_table *l)
{
if(l == NULL)
return;
printf("shuxubiao = [");
for (int i = 0; i < l->length; i++)
{
i && printf(", "); //0位不打印逗号
printf("%d ",l->data[i]); //以数组的方式遍历整个顺序表
}
printf("]\n");
return;
}
int main(int argc, char const *argv[])
{
srand(time(0));
#define max_op 20
sx_table *vec = init_sx_table(max_op);
for (int i = 0; i < max_op; i++)
{
int op = rand() % 4;
int ind = rand() % (vec->length + 3) - 1;
int val = rand() % 100;
switch (op)
{
case 1:
case 2:
case 0:
{
printf("insert %d at %d to Vector = %d\n", val, ind, insert(vec, ind, val));
}
break;
case 3:
{
printf("erase a iterm at %d from Vector = %d\n", ind, earse(vec, ind));
}
break;
default:
break;
}
out(vec);
printf("\n");
}
clear(vec);
#undef max_op
return 0;
}
链表
/************************************
链表:从索引位置插入,从索引位置删除
存储的数据可以翻转
************************************/
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
typedef struct listnode
{
int data;
struct listnode *next;
}ListNode;
typedef struct list
{
ListNode head; /*虚头节点,方便后面55,56行,在往链表零位插入数据时方便,也方便指针移动到要插入节点的前一节点
(这里的移动到是指指针指向要插入节点的前一节点) */
int size; //链表中有多少个节点,不包含上面的虚头节点
}List;
ListNode *GetNewListNode(int val);
List *GetNewList();
void ClearNode(ListNode *);
void ClearList(List *);
int InsertNode(List *, int, int);
int Erase(List*,int);
void OutPut(List *);
void Reverse(List *);
int main(int argc, char const *argv[])
{
srand(time(0));
#define max_op 20
List *vec = GetNewList();
for (int i = 0; i < max_op; i++)
{
int op = rand() % 4;
int ind = rand() % (vec->size + 3) - 1;
int val = rand() % 100;
switch (op)
{
case 1:
case 2:
case 0:
{
printf("insert %d at %d to Vector = %d\n", val, ind, InsertNode(vec, val, ind));
}
break;
case 3:
{
printf("erase a iterm at %d from Vector = %d\n", ind, Erase(vec, ind));
}
break;
default:
break;
}
OutPut(vec);
printf("\n");
}
ClearList(vec);
#undef max_op
return 0;
}
/*
在下方往链表中插入或则擦除某个节点的函数中,一般要用到两个指针来操作,一个指针找到要插入或擦除节点的前一节点,
另一指针主要是辅助抓住后面的链表,防止后面的断掉
*/
ListNode *GetNewListNode(int val) //创建新的节点,主要是在插入节点函数中调用
{
ListNode *P = (ListNode *)malloc(sizeof(ListNode));
P->data = val;
P->next = NULL; //默认没有下一个节点
return P;
}
List *GetNewList() //创建新的链表 ,程序开始调用
{
List *p = (List *)malloc(sizeof(List));
p->head.next = NULL; //默认没有下一个节点
p->size = 0; //默认链表长度为0
return p;
}
int InsertNode(List *l, int val, int ind) //在链表l的ind位置插入一个数据为val的节点,ind从0开始
{
if(l==NULL) //判断链表是否创建
return 0;
if(ind<0 || ind > (l->size)) /*判断插入位置是否超出限制,这里大于而不是大于等于因为刚开始插入时,链表本来就没有节点,所以需要先创建一个节点,
后面要再插入位置就只能往链表内部插入,或者链表最后,也就是说链表大小只能是往后一个节点的增加,
不能一下插入很远的一个位置,不然就会导致中间的有空置 */
return 0;
//printf("Insert new node val:%d at %d", val,ind);
/*下面的整个过程就像给风筝的线中间再接一节线的过程:先是需要一只抓住节点位置的手(这里就是p,起到找到位置防止风筝飞走的作用),
一个要接入的新线(这里就是新的节点node),然后手放在节点前一个位置while的作用,然后再将手放在节点的为位置并将新线node连接到此位置上,
然后将节点前的位置连接到新线并且断开了与节点位置,新线成为新的节点*/
ListNode *p = &(l->head), *node = GetNewListNode(val);//定义两个节点指针对它们好进行操作,第一个辅助节点指针指向虚头节点,第二个指向最新创建的节点
while(ind--) //将p从指向head转到指向要插入节点的前一个节点
p = p->next;
node->next = p->next; //将本来在插入节点的地址赋给新的节点的下一节点指针
p->next = node; //将新的节点的地址给上一节点的下一节点指针
l->size += 1; //将链表的节点数加1
return 1;
}
int Erase(List *l, int ind)
{
if(l == NULL)
return 0;
if(ind<0 || ind >= l->size)
return 0;
ListNode *p = &(l->head), *q;//这里定两只手一只是连接后段的,一只是连接要删掉的
while(ind--)
p = p->next;
q = p->next;
p->next = q->next; //这里直接用p->next = p->next->next,可以让后面一段直接连接到前面,但是中间那个节点会内存泄露
free(q);
l->size -= 1;
return 1;
}
void ClearNode(ListNode *node) //直接释放掉当前节点的内存,主要是释放链表时调用
{
if(node == NULL)
return;
free(node);
return;
}
void OutPut(List *l) //将链表存储的数据打印输出
{
if(l == NULL)
return;
ListNode *p = l->head.next;
printf("List[%d]=[",l->size);
for (int i = 0; i < l->size; i++) //这里用的是while循环和指针不断的访问
{
printf("%d->", p->data);
p = p->next;
}
printf("NULL]\n");
// for (ListNode *p = l->head.next;p; p = p->next) //这一种代码最简洁
// {
// printf("%d", p->data);
// }
}
void Reverse(List *l) //将链表内的存储的数据顺序完全颠倒过来
{
if(l == NULL)
return;
ListNode *p = l->head.next, *q;
/*
为什么这里的p不是指向要插入节点的前一个节点?(可以和上面的相比较一下)
因为这里是头插,插入的位置是固定的,那么就可以用l->head.next表示一直变化的虚头节点后一个节点的地址,就不需要再定义新的变量
*/
l->head.next = NULL;//这里是断开后重新开始插入
while (p)
{
q = p->next; /*先将P指向的下一个保存给临时q,避免下面p指向发生变化丢掉,这里的p与上面插入函数的node是一个意义,都代表要插入节点的内存地址,
只不过一个是新申请的内存一个是打乱再插入,上面函数的p就是l移到某个位置所以和这里的l是一个意思被插入链表中已经存在的地址
*/
p->next = l->head.next; /*将断开后的头节点下一节点地址赋值给重新插入的节点的指向下一个节点的指针
(说人话就是将断开头节点后面的一个节点连接到新查人的节点后面),这里p原来的后面的链表就丢掉了 */
l->head.next = p;//将新插入的节点连接到头节点后面
p = q;
}
return;
}
void ClearList(List *l)
{
if(l == NULL) //如果这里为空就直接返回
return;
ListNode *p = &(l->head),*q; //将虚头节点地址给p
p = p->next; //将下一个地址给p
free(l); //先将虚头节点释放内存
while(p) //通过判断地址指针是否为0来判断是否释放完全
{
q = p->next; //q一个零时变量,因为要释放掉当前指针指向的内存,所以得先把下一个内存的地址赋给零时变量,后面的内存才不会丢失
ClearNode(p);
p = q; //将下一个地址再赋给p一遍循环
}
return;
}