约瑟夫环问题、单链表倒置及求邻近点

1. 约瑟夫环问题

链表: 链表是一组数据项的集合,其中每个数据项都是一个节点的一部分,每个节点都包含指向下一个节点的链接。
此处的约瑟夫环解决方案用于说明,链表是一种抽线,而链表的指针实现是一种具体实现,我们同样可以用索引的方式实现链表。

a. 单链表声明(.h文件):

/*单链表*/
typedef struct node *   link;
struct node{
	int item;
	link next;
};

b. 单链表解决方案:

/***************************************************************
约瑟夫环问题:
	n个人围成一圈,从1编号到n,沿着这个圈每次数M个人就排除对应者,每当有人出列后,剩下的人靠拢,仍然保持一个完整的圆周,找出最后
    剩下的人
****************************************************************/
int josephus_circle(int n,int m)
{
	assert(n>1);
	assert(m >= 1);

	link head;

	head = (link)malloc(n*sizeof(*head));
	assert(head != NULL);

	for (int i = 0; i < n; i++)
	{
		head[i].item = i + 1;
		head[i].next = &head[i + 1];
	}
	head[n - 1].next = &head[0];

	link x = &head[n - 1];
	while (x != x->next)
	{
		for (int i = 1; i < m; i++)
		{
			x = x->next;
		}

		x->next = x->next->next;
	}

	int rlt = x->item;

	free(head);

	return rlt;
}

c. 索引解决方案:

/***************************************************************
约瑟夫环问题:
	用索引模拟链表
****************************************************************/
int josephus_circle2(int n,int m)
{
	typedef struct _element
	{
		int item;
		int next;
	}josephus_element;

	assert(n>1);
	assert(m >= 1);

	josephus_element *head;

	head = (josephus_element *)malloc(n*sizeof(josephus_element));
	assert(head != NULL);

	for (int i = 0; i < n; i++)
	{
		head[i].item = i + 1;
		head[i].next = i + 1;
	}
	head[n - 1].next = 0;

	int x = n - 1;
	while (x != head[x].next)
	{
		for (int i = 1; i < m; i++)
		{
			x = head[x].next;
		}
		head[x].next = head[(head[x].next)].next;
	}

	int rlt = head[x].item;

	free(head);

	return rlt;
}


2. 单链表倒置

/****************************************************************
单链表倒置
****************************************************************/
link list_reverse(link x)
{
    assert(NULL != x);

    link t,y = x,r = NULL;

    while (y != NULL)
    {
        t = y->next;
        y->next = r;
        r = y;
        y = t;
    }

    return r;
}


3. 邻近点对计算

a. 基本方法:

/****************************************************************
邻近点对问题:
对于N个随机产生的单位正方形中的点,统计可以被长度小于d的直线连接
的点的对数。
****************************************************************/
typedef struct{ double x,y; }point;

static double point_distance(point a,point b)
{
	double x = a.x - b.x,y = a.y - b.y;

	return x*x + y*y;
}

/****************************************************************
方案一:
复杂度: O(N*N)
****************************************************************/
int near_point2(int n,double d,double *randdata)
{
	assert(n > 1);
	assert(d > 0 && d < 1.0);
	assert(randdata != NULL);

	point *a = (point *)malloc(n * sizeof(point));
	assert(NULL != a);

	int cnt = 0;
	int index = 0;

	for (int i = 0; i < n; i++)
	{
		a[i].x = randdata[index++];
		a[i].y = randdata[index++];
	}

	double d2 = d*d;
	for (int i = 0; i < n; i++)
	{
		for (int j = i + 1; j < n; j++)
		{
			if (point_distance(a[i],a[j]) < d2)	cnt++;
		}
	}

	free(a);

	return cnt;
}

b. 网格算法

typedef struct _node* _link;
struct _node{ point p; _link next; };
/****************************************************************
二维数组的申请:
	注意此种方法,用此方法可以在代码中使用类似a[i][j]的表达式
****************************************************************/
static _link** malloc2d(int m,int n)
{
	_link** t = (_link**)malloc(m * sizeof(*t));
	assert(t != NULL);

	for (int i = 0; i < m; i++)
	{
		t[i] = (_link *)malloc(n * sizeof(*(t[i])));
		assert(t[i] != NULL);
	}

	return t;
}

static void free2d(_link** t,int n)
{
	for (int i = 0; i < n; i++)
	{
		free(t[i]);
	}

	free(t);
}

/****************************************************************
将点放入网格中:
针对这个应用,可以将统计邻近点的动作也放入此函数中
****************************************************************/
#define INSERT_AND_COUNT    0
#if INSERT_AND_COUNT
static void grid_insert(_link** grid,_link node,int factor,int *cnt,double d)
{
	int X = (int)(node->p.x * factor) + 1;
	int Y = (int)(node->p.y * factor) + 1;

	// 当x或者y为1.0时,应该减1
	if (X == factor + 1)    X -= 1;
	if (Y == factor + 1)    Y -= 1;

	for (int i = X - 1; i <= X + 1; i++)
	{
		for (int j = Y - 1; j <= Y + 1; j++)
		{
			for (_link s = grid[i][j]; s != NULL; s = s->next)
			{
				if (point_distance(node->p,s->p) < d) (*cnt)++;
			}
		}
	}

	node->next = grid[X][Y];
	grid[X][Y] = node;
}
#else
static void grid_insert(_link** grid,int factor)
{
	int X = (int)(node->p.x * factor) + 1;
	int Y = (int)(node->p.y * factor) + 1;

	// 当x或者y为1.0时,应该减1
	if (X == factor + 1)    X -= 1;
	if (Y == factor + 1)    Y -= 1;

	node->next = grid[X][Y];
	grid[X][Y] = node;
}
#endif
/****************************************************************
将单位正方形划分为大小相等的较小的正方形网格。然后对每个正方形网格,
为所有其中的点生成一个链表。通过二维素族,可以立即访问已知点附近的
点集合。
空间复杂度: O(1/d*d + N)
时间复杂度: O(d*d*N*N) 对于小的d值,本算法执行很快
****************************************************************/
int near_point(int n,double *randdata)
{

	int G = (int)(1 / d);

	// 生成网格
	_link** grid = malloc2d(G + 2,G + 2); // 周边加哨兵

	for (int i = 0; i < G + 2; i++)
	{
		for (int j = 0; j < G + 2; j++)
		{
			grid[i][j] = NULL;
		}
	}

	_link head = (_link)malloc(n*sizeof(*head));
	assert(head != NULL);

	double d2 = d*d;
	int cnt = 0;

	// 撒点(并计算)
	int index = 0;
	for (int i = 0; i < n; i++)
	{
		head[i].p.x = randdata[index++];
		head[i].p.y = randdata[index++];
#if INSERT_AND_COUNT
		grid_insert(grid,&head[i],G,&cnt,d2);
#else
		grid_insert(grid,G);
#endif
	}

#if (!INSERT_AND_COUNT)
	for (int i = 1; i < G + 1; i++)     // 不需要考虑哨兵,因此只到G+1
	{
		for (int j = 1; j < G + 1; j++)
		{
			for (_link cur = grid[i][j]; cur != NULL; cur = cur->next)
			{
				// 当前网格链表中的其它元素
				for (_link tmp = cur->next; tmp != NULL; tmp = tmp->next)
				{
					if (point_distance(cur->p,tmp->p) < d2) cnt++;
				}

				// 右边网格链表
				for (_link tmp = grid[i][j + 1]; tmp != NULL; tmp = tmp->next)
				{
					if (point_distance(cur->p,tmp->p) < d2) cnt++;
				}

				// 左下网格链表
				for (_link tmp = grid[i + 1][j - 1]; tmp != NULL; tmp = tmp->next)
				{
					if (point_distance(cur->p,tmp->p) < d2) cnt++;
				}

				// 下方网格链表
				for (_link tmp = grid[i + 1][j]; tmp != NULL; tmp = tmp->next)
				{
					if (point_distance(cur->p,tmp->p) < d2) cnt++;
				}

				// 右下方网格链表
				for (_link tmp = grid[i + 1][j + 1]; tmp != NULL; tmp = tmp->next)
				{
					if (point_distance(cur->p,tmp->p) < d2) cnt++;
				}

			}
		}
	}
#endif

	free(head);
	free2d(grid,G + 2);

	return cnt;
}

其中使用INSERT_AND_COUNT宏控制是否在插入时计算,经实测试,在插入时计算要稍快一些。





相关文章

什么是设计模式一套被反复使用、多数人知晓的、经过分类编目...
单一职责原则定义(Single Responsibility Principle,SRP)...
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强...
适配器模式将一个类的接口转换成客户期望的另一个接口,使得...
策略模式定义了一系列算法族,并封装在类中,它们之间可以互...
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,...